<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"><title>Netty入门-第一话 | 石银的小酒屋</title><meta name="keywords" content="Netty"><meta name="author" content="Alix"><meta name="copyright" content="Alix"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="第一话对BIO和NIO进行了讲解，为后续做准备。">
<meta property="og:type" content="article">
<meta property="og:title" content="Netty入门-第一话">
<meta property="og:url" content="http://example.com/2022/04/08/netty/Netty%E5%85%A5%E9%97%A8-%E7%AC%AC%E4%B8%80%E8%AF%9D/index.html">
<meta property="og:site_name" content="石银的小酒屋">
<meta property="og:description" content="第一话对BIO和NIO进行了讲解，为后续做准备。">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://npm.elemecdn.com/lql_static@latest/logo/netty_logo.jpg">
<meta property="article:published_time" content="2022-04-08T06:21:58.000Z">
<meta property="article:modified_time" content="2023-04-20T12:24:17.644Z">
<meta property="article:author" content="Alix">
<meta property="article:tag" content="Netty">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://npm.elemecdn.com/lql_static@latest/logo/netty_logo.jpg"><link rel="shortcut icon" href="/assetse/%E5%A4%B4%E5%83%8F.jpg"><link rel="canonical" href="http://example.com/2022/04/08/netty/Netty%E5%85%A5%E9%97%A8-%E7%AC%AC%E4%B8%80%E8%AF%9D/"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.min.css" media="print" onload="this.media='all'"><script>const GLOBAL_CONFIG = { 
  root: '/',
  algolia: undefined,
  localSearch: undefined,
  translate: undefined,
  noticeOutdate: undefined,
  highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":false},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: false,
    post: false
  },
  runtime: '',
  date_suffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: undefined,
  lightbox: 'fancybox',
  Snackbar: undefined,
  source: {
    justifiedGallery: {
      js: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.js',
      css: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.css'
    }
  },
  isPhotoFigcaption: false,
  islazyload: false,
  isAnchor: false
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
  title: 'Netty入门-第一话',
  isPost: true,
  isHome: false,
  isHighlightShrink: false,
  isToc: true,
  postUpdate: '2023-04-20 20:24:17'
}</script><noscript><style type="text/css">
  #nav {
    opacity: 1
  }
  .justified-gallery img {
    opacity: 1
  }

  #recent-posts time,
  #post-meta time {
    display: inline !important
  }
</style></noscript><script>(win=>{
    win.saveToLocal = {
      set: function setWithExpiry(key, value, ttl) {
        if (ttl === 0) return
        const now = new Date()
        const expiryDay = ttl * 86400000
        const item = {
          value: value,
          expiry: now.getTime() + expiryDay,
        }
        localStorage.setItem(key, JSON.stringify(item))
      },

      get: function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)

        if (!itemStr) {
          return undefined
        }
        const item = JSON.parse(itemStr)
        const now = new Date()

        if (now.getTime() > item.expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return item.value
      }
    }
  
    win.getScript = url => new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      script.async = true
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    })
  
      win.activateDarkMode = function () {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = function () {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
          if (t === 'dark') activateDarkMode()
          else if (t === 'light') activateLightMode()
        
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    
    const detectApple = () => {
      if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
        document.documentElement.classList.add('apple')
      }
    }
    detectApple()
    })(window)</script><link rel="stylesheet" href="/self/Kimbiedark.css"><meta name="generator" content="Hexo 6.2.0"><link rel="alternate" href="/atom.xml" title="石银的小酒屋" type="application/atom+xml">
</head><body><div id="loading-box"><div class="loading-left-bg"></div><div class="loading-right-bg"></div><div class="spinner-box"><div class="configure-border-1"><div class="configure-core"></div></div><div class="configure-border-2"><div class="configure-core"></div></div><div class="loading-word">加载中...</div></div></div><div id="web_bg"></div><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img is-center"><img src="/assetse/%E5%A4%B4%E5%83%8F.jpg" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="sidebar-site-data site-data is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">23</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">9</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">7</div></a></div><hr/><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 主页</span></a></div><div class="menus_item"><a class="site-page group" href="javascript:void(0);"><i class="fa-fw fa fa-graduation-cap"></i><span> 博文</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/categories/"><i class="fa-fw fa fa-archive"></i><span> 分类</span></a></li><li><a class="site-page child" href="/tags/"><i class="fa-fw fa fa-tags"></i><span> 标签</span></a></li><li><a class="site-page child" href="/archives/"><i class="fa-fw fa fa-folder-open"></i><span> 归档</span></a></li></ul></div><div class="menus_item"><a class="site-page group" href="javascript:void(0);"><i class="fa-fw fas fa-list"></i><span> 生活</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/photos/"><i class="fa-fw fa fa-camera-retro"></i><span> 相册</span></a></li><li><a class="site-page child" href="/playlist/"><i class="fa-fw fa fa-music"></i><span> 音乐</span></a></li><li><a class="site-page child" href="/bangumis/"><i class="fa-fw fas fa-video"></i><span> 番剧</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="/link/"><i class="fa-fw fa fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fas fa-heart"></i><span> 关于笔者</span></a></div></div></div></div><div class="post" id="body-wrap"><header class="post-bg" id="page-header" style="background-image: url('https://npm.elemecdn.com/lql_static@latest/logo/netty_logo.jpg')"><nav id="nav"><span id="blog_name"><a id="site-name" href="/">石银的小酒屋</a></span><div id="menus"><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 主页</span></a></div><div class="menus_item"><a class="site-page group" href="javascript:void(0);"><i class="fa-fw fa fa-graduation-cap"></i><span> 博文</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/categories/"><i class="fa-fw fa fa-archive"></i><span> 分类</span></a></li><li><a class="site-page child" href="/tags/"><i class="fa-fw fa fa-tags"></i><span> 标签</span></a></li><li><a class="site-page child" href="/archives/"><i class="fa-fw fa fa-folder-open"></i><span> 归档</span></a></li></ul></div><div class="menus_item"><a class="site-page group" href="javascript:void(0);"><i class="fa-fw fas fa-list"></i><span> 生活</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/photos/"><i class="fa-fw fa fa-camera-retro"></i><span> 相册</span></a></li><li><a class="site-page child" href="/playlist/"><i class="fa-fw fa fa-music"></i><span> 音乐</span></a></li><li><a class="site-page child" href="/bangumis/"><i class="fa-fw fas fa-video"></i><span> 番剧</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="/link/"><i class="fa-fw fa fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fas fa-heart"></i><span> 关于笔者</span></a></div></div><div id="toggle-menu"><a class="site-page"><i class="fas fa-bars fa-fw"></i></a></div></div></nav><div id="post-info"><h1 class="post-title">Netty入门-第一话</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2022-04-08T06:21:58.000Z" title="发表于 2022-04-08 14:21:58">2022-04-08</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2023-04-20T12:24:17.644Z" title="更新于 2023-04-20 20:24:17">2023-04-20</time></span><span class="post-meta-categories"><span class="post-meta-separator">|</span><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/Netty/">Netty</a><i class="fas fa-angle-right post-meta-separator"></i><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/Netty/%E5%85%A5%E9%97%A8/">入门</a></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-wordcount"><i class="far fa-file-word fa-fw post-meta-icon"></i><span class="post-meta-label">字数总计:</span><span class="word-count">9.6k</span><span class="post-meta-separator">|</span><i class="far fa-clock fa-fw post-meta-icon"></i><span class="post-meta-label">阅读时长:</span><span>37分钟</span></span><span class="post-meta-separator">|</span><span class="post-meta-pv-cv" id="" data-flag-title="Netty入门-第一话"><i class="far fa-eye fa-fw post-meta-icon"></i><span class="post-meta-label">阅读量:</span><span id="busuanzi_value_page_pv"><i class="fa-solid fa-spinner fa-spin"></i></span></span></div></div></div></header><main class="layout" id="content-inner"><div id="post"><article class="post-content" id="article-container"><blockquote>
<ul>
<li>因为学netty的过程中，发现计算机网络和操作系统蛮重要的，所以接下来会写几篇这方面的文章，希望读者不要觉得我东写一点，西写一点。</li>
<li>比如netty中有操作系统里的IO，零拷贝。有计算机网络里的通信(因为netty本身就是一个网络应用框架)。等等</li>
</ul>
</blockquote>
<h1 id="Netty简介"><a href="#Netty简介" class="headerlink" title="Netty简介"></a>Netty简介</h1><h2 id="Netty-的介绍"><a href="#Netty-的介绍" class="headerlink" title="Netty 的介绍"></a>Netty 的介绍</h2><ol>
<li><code>Netty</code> 是由 <code>JBOSS</code> 提供的一个 <code>Java</code> 开源框架，现为 <code>Github</code> 上的独立项目。</li>
<li><code>Netty</code> 是一个异步的、基于事件驱动的网络应用框架，用以快速开发高性能、高可靠性的网络 <code>IO</code> 程序。</li>
<li><code>Netty</code> 主要针对在 <code>TCP</code> 协议下，面向 <code>Client</code> 端的高并发应用，或者 <code>Peer-to-Peer</code> 场景下的大量数据持续传输的应用。</li>
<li><code>Netty</code> 本质是一个 <code>NIO</code> 框架，适用于服务器通讯相关的多种应用场景。</li>
<li>要透彻理解 <code>Netty</code>，需要先学习 <code>NIO</code>，这样我们才能阅读 <code>Netty</code> 的源码。</li>
</ol>
<p>相对简单的一个体系图</p>
<img src="https://npm.elemecdn.com/youthlql@1.0.0/netty/introduction/chapter_001/0001.png"/>

<h2 id="Netty-的应用场景"><a href="#Netty-的应用场景" class="headerlink" title="Netty 的应用场景"></a>Netty 的应用场景</h2><h3 id="互联网行业"><a href="#互联网行业" class="headerlink" title="互联网行业"></a>互联网行业</h3><ol>
<li>互联网行业：在分布式系统中，各个节点之间需要远程服务调用，高性能的 <code>RPC</code> 框架必不可少，<code>Netty</code> 作为异步高性能的通信框架，往往作为基础通信组件被这些 <code>RPC</code> 框架使用。</li>
<li>典型的应用有：阿里分布式服务框架 <code>Dubbo</code> 的 <code>RPC</code> 框 架使用 <code>Dubbo</code> 协议进行节点间通信，<code>Dubbo</code> 协议默认使用 <code>Netty</code> 作为基础通信组件，用于实现各进程节点之间的内部通信。</li>
</ol>
<h3 id="游戏行业"><a href="#游戏行业" class="headerlink" title="游戏行业"></a>游戏行业</h3><ol>
<li>无论是手游服务端还是大型的网络游戏，<code>Java</code> 语言得到了越来越广泛的应用。</li>
<li><code>Netty</code> 作为高性能的基础通信组件，提供了 <code>TCP/UDP</code> 和 <code>HTTP</code> 协议栈，方便定制和开发私有协议栈，账号登录服务器。</li>
<li>地图服务器之间可以方便的通过 <code>Netty</code> 进行高性能的通信。</li>
</ol>
<h3 id="大数据领域"><a href="#大数据领域" class="headerlink" title="大数据领域"></a>大数据领域</h3><ol>
<li>经典的 <code>Hadoop</code> 的高性能通信和序列化组件 <code>Avro</code> 的 <code>RPC</code> 框架，默认采用 <code>Netty</code> 进行跨界点通信。</li>
<li>它的 <code>NettyService</code> 基于 <code>Netty</code> 框架二次封装实现。</li>
</ol>
<h3 id="其它开源项目使用到-Netty"><a href="#其它开源项目使用到-Netty" class="headerlink" title="其它开源项目使用到 Netty"></a>其它开源项目使用到 Netty</h3><p>网址：<a target="_blank" rel="noopener" href="https://netty.io/wiki/related-projects.html">https://netty.io/wiki/related-projects.html</a></p>
<h2 id="Netty-的学习资料参考"><a href="#Netty-的学习资料参考" class="headerlink" title="Netty 的学习资料参考"></a>Netty 的学习资料参考</h2><img src="https://npm.elemecdn.com/youthlql@1.0.0/netty/introduction/chapter_001/0002.png"/>















<h1 id="Java-BIO编程"><a href="#Java-BIO编程" class="headerlink" title="Java BIO编程"></a>Java BIO编程</h1><h2 id="I-x2F-O-模型"><a href="#I-x2F-O-模型" class="headerlink" title="I&#x2F;O 模型"></a>I&#x2F;O 模型</h2><ol>
<li><code>I/O</code> 模型简单的理解：就是用什么样的通道进行数据的发送和接收，很大程度上决定了程序通信的性能。</li>
<li><code>Java</code> 共支持 <code>3</code> 种网络编程模型 <code>I/O</code> 模式：<code>BIO</code>、<code>NIO</code>、<code>AIO</code>。</li>
<li><code>Java BIO</code>：同步并阻塞（传统阻塞型），服务器实现模式为一个连接一个线程，即客户端有连接请求时服务器端就需要启动一个线程进行处理，如果这个连接不做任何事情会造成不必要的线程开销。【简单示意图】</li>
</ol>
<img src="https://npm.elemecdn.com/youthlql@1.0.0/netty/introduction/chapter_001/0003.png"/>

<ol start="4">
<li><code>Java NIO</code>：同步非阻塞，服务器实现模式为一个线程处理多个请求（连接），即客户端发送的连接请求都会注册到多路复用器上，多路复用器轮询到连接有 <code>I/O</code> 请求就进行处理。【简单示意图】</li>
</ol>
<img src="https://npm.elemecdn.com/youthlql@1.0.0/netty/introduction/chapter_001/0004.png"/>

<ol start="5">
<li><code>Java AIO(NIO.2)</code>：异步非阻塞，<code>AIO</code> 引入异步通道的概念，采用了 <code>Proactor</code> 模式，简化了程序编写，有效的请求才启动线程，它的特点是先由操作系统完成后才通知服务端程序启动线程去处理，一般适用于连接数较多且连接时间较长的应用。</li>
<li>我们依次展开讲解。</li>
</ol>
<h2 id="BIO、NIO、AIO-使用场景分析"><a href="#BIO、NIO、AIO-使用场景分析" class="headerlink" title="BIO、NIO、AIO 使用场景分析"></a>BIO、NIO、AIO 使用场景分析</h2><ol>
<li><code>BIO</code> 方式适用于连接数目比较小且固定的架构，这种方式对服务器资源要求比较高，并发局限于应用中，<code>JDK1.4</code> 以前的唯一选择，但程序简单易理解。</li>
<li><code>NIO</code> 方式适用于连接数目多且连接比较短（轻操作）的架构，比如聊天服务器，弹幕系统，服务器间通讯等。编程比较复杂，<code>JDK1.4</code> 开始支持。</li>
<li><code>AIO</code> 方式使用于连接数目多且连接比较长（重操作）的架构，比如相册服务器，充分调用 <code>OS</code> 参与并发操作，编程比较复杂，<code>JDK7</code> 开始支持。</li>
</ol>
<h2 id="Java-BIO-基本介绍"><a href="#Java-BIO-基本介绍" class="headerlink" title="Java BIO 基本介绍"></a>Java BIO 基本介绍</h2><ol>
<li><code>Java BIO</code> 就是传统的 <code>Java I/O</code> 编程，其相关的类和接口在 <code>java.io</code>。</li>
<li><code>BIO(BlockingI/O)</code>：同步阻塞，服务器实现模式为一个连接一个线程，即客户端有连接请求时服务器端就需要启动一个线程进行处理，如果这个连接不做任何事情会造成不必要的线程开销，可以通过线程池机制改善（实现多个客户连接服务器）。【后有应用实例】</li>
<li><code>BIO</code> 方式适用于连接数目比较小且固定的架构，这种方式对服务器资源要求比较高，并发局限于应用中，<code>JDK1.4</code> 以前的唯一选择，程序简单易理解。</li>
</ol>
<h2 id="Java-BIO-工作机制"><a href="#Java-BIO-工作机制" class="headerlink" title="Java BIO 工作机制"></a>Java BIO 工作机制</h2><img src="https://npm.elemecdn.com/youthlql@1.0.0/netty/introduction/chapter_001/0005.png"/>

<p>对 <code>BIO</code> 编程流程的梳理</p>
<ol>
<li>服务器端启动一个 <code>ServerSocket</code>。</li>
<li>客户端启动 <code>Socket</code> 对服务器进行通信，默认情况下服务器端需要对每个客户建立一个线程与之通讯。</li>
<li>客户端发出请求后，先咨询服务器是否有线程响应，如果没有则会等待，或者被拒绝。</li>
<li>如果有响应，客户端线程会等待请求结束后，再继续执行。</li>
</ol>
<h2 id="Java-BIO-应用实例"><a href="#Java-BIO-应用实例" class="headerlink" title="Java BIO 应用实例"></a>Java BIO 应用实例</h2><p>实例说明：</p>
<ol>
<li>使用 <code>BIO</code> 模型编写一个服务器端，监听 <code>6666</code> 端口，当有客户端连接时，就启动一个线程与之通讯。</li>
<li>要求使用线程池机制改善，可以连接多个客户端。</li>
<li>服务器端可以接收客户端发送的数据（<code>telnet</code> 方式即可）。</li>
<li>代码演示：</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.bio;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.InputStream;</span><br><span class="line"><span class="keyword">import</span> java.net.ServerSocket;</span><br><span class="line"><span class="keyword">import</span> java.net.Socket;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.ExecutorService;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.Executors;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BIOServer</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="comment">//线程池机制</span></span><br><span class="line">        <span class="comment">//思路</span></span><br><span class="line">        <span class="comment">//1. 创建一个线程池</span></span><br><span class="line">        <span class="comment">//2. 如果有客户端连接，就创建一个线程，与之通讯(单独写一个方法)</span></span><br><span class="line">        <span class="type">ExecutorService</span> <span class="variable">newCachedThreadPool</span> <span class="operator">=</span> Executors.newCachedThreadPool();</span><br><span class="line">        <span class="comment">//创建ServerSocket</span></span><br><span class="line">        <span class="type">ServerSocket</span> <span class="variable">serverSocket</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServerSocket</span>(<span class="number">6666</span>);</span><br><span class="line">        System.out.println(<span class="string">&quot;服务器启动了&quot;</span>);</span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;线程信息id = &quot;</span> + Thread.currentThread().getId() + <span class="string">&quot;名字 = &quot;</span> + Thread.currentThread().getName());</span><br><span class="line">            <span class="comment">//监听，等待客户端连接</span></span><br><span class="line">            System.out.println(<span class="string">&quot;等待连接....&quot;</span>);</span><br><span class="line">            <span class="comment">//会阻塞在accept()</span></span><br><span class="line">            <span class="keyword">final</span> <span class="type">Socket</span> <span class="variable">socket</span> <span class="operator">=</span> serverSocket.accept();</span><br><span class="line">            System.out.println(<span class="string">&quot;连接到一个客户端&quot;</span>);</span><br><span class="line">            <span class="comment">//就创建一个线程，与之通讯(单独写一个方法)</span></span><br><span class="line">            newCachedThreadPool.execute(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">                <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;<span class="comment">//我们重写</span></span><br><span class="line">                    <span class="comment">//可以和客户端通讯</span></span><br><span class="line">                    handler(socket);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//编写一个handler方法，和客户端通讯</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">handler</span><span class="params">(Socket socket)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;线程信息id = &quot;</span> + Thread.currentThread().getId() + <span class="string">&quot;名字 = &quot;</span> + Thread.currentThread().getName());</span><br><span class="line">            <span class="type">byte</span>[] bytes = <span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">1024</span>];</span><br><span class="line">            <span class="comment">//通过socket获取输入流</span></span><br><span class="line">            <span class="type">InputStream</span> <span class="variable">inputStream</span> <span class="operator">=</span> socket.getInputStream();</span><br><span class="line">            <span class="comment">//循环的读取客户端发送的数据</span></span><br><span class="line">            <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">                System.out.println(<span class="string">&quot;线程信息id = &quot;</span> + Thread.currentThread().getId() + <span class="string">&quot;名字 = &quot;</span> + Thread.currentThread().getName());</span><br><span class="line">                System.out.println(<span class="string">&quot;read....&quot;</span>);</span><br><span class="line">                <span class="type">int</span> <span class="variable">read</span> <span class="operator">=</span> inputStream.read(bytes);</span><br><span class="line">                <span class="keyword">if</span> (read != -<span class="number">1</span>) &#123;</span><br><span class="line">                    System.out.println(<span class="keyword">new</span> <span class="title class_">String</span>(bytes, <span class="number">0</span>, read));<span class="comment">//输出客户端发送的数据</span></span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;关闭和client的连接&quot;</span>);</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                socket.close();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<img src="https://npm.elemecdn.com/youthlql@1.0.0/netty/introduction/chapter_001/0006.png"/>



<h2 id="问题分析"><a href="#问题分析" class="headerlink" title="问题分析"></a>问题分析</h2><ol>
<li>每个请求都需要创建独立的线程，与对应的客户端进行数据 <code>Read</code>，业务处理，数据 <code>Write</code>。</li>
<li>当并发数较大时，需要创建大量线程来处理连接，系统资源占用较大。</li>
<li>连接建立后，如果当前线程暂时没有数据可读，则线程就阻塞在 <code>Read</code> 操作上，造成线程资源浪费。</li>
</ol>
<h1 id="Java-NIO编程"><a href="#Java-NIO编程" class="headerlink" title="Java NIO编程"></a>Java NIO编程</h1><h2 id="Java-NIO-基本介绍"><a href="#Java-NIO-基本介绍" class="headerlink" title="Java NIO 基本介绍"></a>Java NIO 基本介绍</h2><ol>
<li><code>Java NIO</code> 全称 <code>Java non-blocking IO</code>，是指 <code>JDK</code> 提供的新 <code>API</code>。从 <code>JDK1.4</code> 开始，<code>Java</code> 提供了一系列改进的输入&#x2F;输出的新特性，被统称为 <code>NIO</code>（即 <code>NewIO</code>），是同步非阻塞的。</li>
<li><code>NIO</code> 相关类都被放在 <code>java.nio</code> 包及子包下，并且对原 <code>java.io</code> 包中的很多类进行改写。【基本案例】</li>
<li><code>NIO</code> 有三大核心部分：**<code>Channel</code>（通道）、<code>Buffer</code>（缓冲区）、<code>Selector</code>（选择器）** 。</li>
<li><code>NIO</code> 是<strong>面向缓冲区，或者面向块编程</strong>的。数据读取到一个它稍后处理的缓冲区，需要时可在缓冲区中前后移动，这就增加了处理过程中的灵活性，使用它可以提供非阻塞式的高伸缩性网络。</li>
<li><code>Java NIO</code> 的非阻塞模式，使一个线程从某通道发送请求或者读取数据，但是它仅能得到目前可用的数据，如果目前没有数据可用时，就什么都不会获取，而不是保持线程阻塞，所以直至数据变的可以读取之前，该线程可以继续做其他的事情。非阻塞写也是如此，一个线程请求写入一些数据到某通道，但不需要等待它完全写入，这个线程同时可以去做别的事情。【后面有案例说明】</li>
<li>通俗理解：<code>NIO</code> 是可以做到用一个线程来处理多个操作的。假设有 <code>10000</code> 个请求过来,根据实际情况，可以分配 <code>50</code> 或者 <code>100</code> 个线程来处理。不像之前的阻塞 <code>IO</code> 那样，非得分配 <code>10000</code> 个。</li>
<li><code>HTTP 2.0</code> 使用了多路复用的技术，做到同一个连接并发处理多个请求，而且并发请求的数量比 <code>HTTP 1.1</code> 大了好几个数量级。</li>
<li>案例说明 <code>NIO</code> 的 <code>Buffer</code></li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.nio;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.nio.IntBuffer;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BasicBuffer</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//举例说明 Buffer 的使用(简单说明)</span></span><br><span class="line">        <span class="comment">//创建一个 Buffer，大小为 5，即可以存放 5 个 int</span></span><br><span class="line">        <span class="type">IntBuffer</span> <span class="variable">intBuffer</span> <span class="operator">=</span> IntBuffer.allocate(<span class="number">5</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//向buffer存放数据</span></span><br><span class="line">        <span class="comment">//intBuffer.put(10);</span></span><br><span class="line">        <span class="comment">//intBuffer.put(11);</span></span><br><span class="line">        <span class="comment">//intBuffer.put(12);</span></span><br><span class="line">        <span class="comment">//intBuffer.put(13);</span></span><br><span class="line">        <span class="comment">//intBuffer.put(14);</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; intBuffer.capacity(); i++) &#123;</span><br><span class="line">            intBuffer.put(i * <span class="number">2</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//如何从 buffer 读取数据</span></span><br><span class="line">        <span class="comment">//将 buffer 转换，读写切换(!!!)</span></span><br><span class="line">        intBuffer.flip();</span><br><span class="line">        <span class="keyword">while</span> (intBuffer.hasRemaining()) &#123;</span><br><span class="line">            System.out.println(intBuffer.get());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="NIO-和-BIO-的比较"><a href="#NIO-和-BIO-的比较" class="headerlink" title="NIO 和 BIO 的比较"></a>NIO 和 BIO 的比较</h2><ol>
<li><code>BIO</code> 以流的方式处理数据，而 <code>NIO</code> 以块的方式处理数据，块 <code>I/O</code> 的效率比流 <code>I/O</code> 高很多。</li>
<li><code>BIO</code> 是阻塞的，<code>NIO</code> 则是非阻塞的。</li>
<li><code>BIO</code> 基于字节流和字符流进行操作，而 <code>NIO</code> 基于 <code>Channel</code>（通道）和 <code>Buffer</code>（缓冲区）进行操作，数据总是从通道读取到缓冲区中，或者从缓冲区写入到通道中。<code>Selector</code>（选择器）用于监听多个通道的事件（比如：连接请求，数据到达等），因此使用单个线程就可以监听多个客户端通道。</li>
<li>Buffer和Channel之间的数据流向是双向的</li>
</ol>
<img src="https://npm.elemecdn.com/youthlql@1.0.0/netty/introduction/chapter_001/0007.png"/>

<h2 id="NIO-三大核心原理示意图"><a href="#NIO-三大核心原理示意图" class="headerlink" title="NIO 三大核心原理示意图"></a>NIO 三大核心原理示意图</h2><p>一张图描述 <code>NIO</code> 的 <code>Selector</code>、<code>Channel</code> 和 <code>Buffer</code> 的关系。</p>
<h3 id="Selector、Channel-和-Buffer-关系图（简单版）"><a href="#Selector、Channel-和-Buffer-关系图（简单版）" class="headerlink" title="Selector、Channel 和 Buffer 关系图（简单版）"></a>Selector、Channel 和 Buffer 关系图（简单版）</h3><p>关系图的说明:</p>
<img src="https://npm.elemecdn.com/youthlql@1.0.0/netty/introduction/chapter_001/0008.png"/>

<ol>
<li>每个 <code>Channel</code> 都会对应一个 <code>Buffer</code>。</li>
<li><code>Selector</code> 对应一个线程，一个线程对应多个 <code>Channel</code>（连接）。</li>
<li>该图反应了有三个 <code>Channel</code> 注册到该 <code>Selector</code> &#x2F;&#x2F;程序</li>
<li>程序切换到哪个 <code>Channel</code> 是由事件决定的，<code>Event</code> 就是一个重要的概念。</li>
<li><code>Selector</code> 会根据不同的事件，在各个通道上切换。</li>
<li><code>Buffer</code> 就是一个内存块，底层是有一个数组。</li>
<li>数据的读取写入是通过 <code>Buffer</code>，这个和 <code>BIO</code>是不同的，<code>BIO</code> 中要么是输入流，或者是输出流，不能双向，但是 <code>NIO</code> 的 <code>Buffer</code> 是可以读也可以写，需要 <code>flip</code> 方法切换 <code>Channel</code> 是双向的，可以返回底层操作系统的情况，比如 <code>Linux</code>，底层的操作系统通道就是双向的。</li>
</ol>
<h2 id="缓冲区（Buffer）"><a href="#缓冲区（Buffer）" class="headerlink" title="缓冲区（Buffer）"></a>缓冲区（Buffer）</h2><h3 id="基本介绍"><a href="#基本介绍" class="headerlink" title="基本介绍"></a>基本介绍</h3><p>缓冲区（<code>Buffer</code>）：缓冲区本质上是一个<strong>可以读写数据的内存块</strong>，可以理解成是一个<strong>容器对象（含数组）</strong>，该对象提供了一组方法，可以更轻松地使用内存块，，缓冲区对象内置了一些机制，能够跟踪和记录缓冲区的状态变化情况。<code>Channel</code> 提供从文件、网络读取数据的渠道，但是读取或写入的数据都必须经由 <code>Buffer</code>，如图:【后面举例说明】</p>
<img src="https://npm.elemecdn.com/youthlql@1.0.0/netty/introduction/chapter_001/0009.png"/>

<h3 id="Buffer-类及其子类"><a href="#Buffer-类及其子类" class="headerlink" title="Buffer 类及其子类"></a>Buffer 类及其子类</h3><ol>
<li>在 <code>NIO</code> 中，<code>Buffer</code> 是一个顶层父类，它是一个抽象类，类的层级关系图：</li>
</ol>
<img src="https://npm.elemecdn.com/youthlql@1.0.0/netty/introduction/chapter_001/0010.png" />

<ol start="2">
<li><code>Buffer</code> 类定义了所有的缓冲区都具有的四个属性来提供关于其所包含的数据元素的信息：</li>
</ol>
<img src="https://npm.elemecdn.com/youthlql@1.0.0/netty/introduction/chapter_001/0011.png"/>





<ol start="3">
<li><code>Buffer</code> 类相关方法一览</li>
</ol>
<img src="https://npm.elemecdn.com/youthlql@1.0.0/netty/introduction/chapter_001/0013.png" />

<h3 id="ByteBuffer"><a href="#ByteBuffer" class="headerlink" title="ByteBuffer"></a>ByteBuffer</h3><p>从前面可以看出对于 <code>Java</code> 中的基本数据类型（<code>boolean</code> 除外），都有一个 <code>Buffer</code> 类型与之相对应，最常用的自然是 <code>ByteBuffer</code> 类（二进制数据），该类的主要方法如下：</p>
<img src="https://npm.elemecdn.com/youthlql@1.0.0/netty/introduction/chapter_001/0014.png"/>

<h2 id="通道（Channel）"><a href="#通道（Channel）" class="headerlink" title="通道（Channel）"></a>通道（Channel）</h2><h3 id="基本介绍-1"><a href="#基本介绍-1" class="headerlink" title="基本介绍"></a>基本介绍</h3><ol>
<li><code>NIO</code> 的通道类似于流，但有些区别如下：<ul>
<li>通道可以同时进行读写，而流只能读或者只能写</li>
<li>通道可以实现异步读写数据</li>
<li>通道可以从缓冲读数据，也可以写数据到缓冲:</li>
</ul>
</li>
<li><code>BIO</code> 中的 <code>Stream</code> 是单向的，例如 <code>FileInputStream</code> 对象只能进行读取数据的操作，而 <code>NIO</code> 中的通道（<code>Channel</code>）是双向的，可以读操作，也可以写操作。</li>
<li><code>Channel</code> 在 <code>NIO</code> 中是一个接口 <code>public interface Channel extends Closeable&#123;&#125;</code></li>
<li>常用的 <code>Channel</code> 类有：**<code>FileChannel</code>、<code>DatagramChannel</code>、<code>ServerSocketChannel</code> 和 <code>SocketChannel</code>**。【<code>ServerSocketChanne</code> 类似 <code>ServerSocket</code>、<code>SocketChannel</code> 类似 <code>Socket</code>】</li>
<li><code>FileChannel</code> 用于文件的数据读写，<code>DatagramChannel</code> 用于 <code>UDP</code> 的数据读写，<code>ServerSocketChannel</code> 和 <code>SocketChannel</code> 用于 <code>TCP</code> 的数据读写。</li>
<li>图示</li>
</ol>
<img src="https://npm.elemecdn.com/youthlql@1.0.0/netty/introduction/chapter_001/0015.png"/>

<h3 id="FileChannel-类"><a href="#FileChannel-类" class="headerlink" title="FileChannel 类"></a>FileChannel 类</h3><p><code>FileChannel</code> 主要用来对本地文件进行 <code>IO</code> 操作，常见的方法有</p>
<ul>
<li><code>public int read(ByteBuffer dst)</code>，从通道读取数据并放到缓冲区中</li>
<li><code>public int write(ByteBuffer src)</code>，把缓冲区的数据写到通道中</li>
<li><code>public long transferFrom(ReadableByteChannel src, long position, long count)</code>，从目标通道中复制数据到当前通道</li>
<li><code>public long transferTo(long position, long count, WritableByteChannel target)</code>，把数据从当前通道复制给目标通道</li>
</ul>
<h3 id="应用实例1-本地文件写数据"><a href="#应用实例1-本地文件写数据" class="headerlink" title="应用实例1 - 本地文件写数据"></a>应用实例1 - 本地文件写数据</h3><p>实例要求：</p>
<ol>
<li>使用前面学习后的 <code>ByteBuffer</code>（缓冲）和 <code>FileChannel</code>（通道），将 “hello,尚硅谷” 写入到 <code>file01.txt</code> 中</li>
<li>文件不存在就创建</li>
<li>代码演示</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.nio;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.FileOutputStream;</span><br><span class="line"><span class="keyword">import</span> java.nio.ByteBuffer;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.FileChannel;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NIOFileChannel01</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> <span class="string">&quot;hello,尚硅谷&quot;</span>;</span><br><span class="line">        <span class="comment">//创建一个输出流 -&gt; channel</span></span><br><span class="line">        <span class="type">FileOutputStream</span> <span class="variable">fileOutputStream</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileOutputStream</span>(<span class="string">&quot;d:\\file01.txt&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//通过 fileOutputStream 获取对应的 FileChannel</span></span><br><span class="line">        <span class="comment">//这个 fileChannel 真实类型是 FileChannelImpl</span></span><br><span class="line">        <span class="type">FileChannel</span> <span class="variable">fileChannel</span> <span class="operator">=</span> fileOutputStream.getChannel();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//创建一个缓冲区 ByteBuffer</span></span><br><span class="line">        <span class="type">ByteBuffer</span> <span class="variable">byteBuffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">1024</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//将 str 放入 byteBuffer</span></span><br><span class="line">        byteBuffer.put(str.getBytes());</span><br><span class="line"></span><br><span class="line">        <span class="comment">//对 byteBuffer 进行 flip</span></span><br><span class="line">        byteBuffer.flip();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//将 byteBuffer 数据写入到 fileChannel</span></span><br><span class="line">        fileChannel.write(byteBuffer);</span><br><span class="line">        fileOutputStream.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="应用实例2-本地文件读数据"><a href="#应用实例2-本地文件读数据" class="headerlink" title="应用实例2 - 本地文件读数据"></a>应用实例2 - 本地文件读数据</h3><p>实例要求：</p>
<ol>
<li>使用前面学习后的 <code>ByteBuffer</code>（缓冲）和 <code>FileChannel</code>（通道），将 <code>file01.txt</code> 中的数据读入到程序，并显示在控制台屏幕</li>
<li>假定文件已经存在</li>
<li>代码演示</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.nio;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.File;</span><br><span class="line"><span class="keyword">import</span> java.io.FileInputStream;</span><br><span class="line"><span class="keyword">import</span> java.nio.ByteBuffer;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.FileChannel;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NIOFileChannel02</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//创建文件的输入流</span></span><br><span class="line">        <span class="type">File</span> <span class="variable">file</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">File</span>(<span class="string">&quot;d:\\file01.txt&quot;</span>);</span><br><span class="line">        <span class="type">FileInputStream</span> <span class="variable">fileInputStream</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileInputStream</span>(file);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//通过 fileInputStream 获取对应的 FileChannel -&gt; 实际类型 FileChannelImpl</span></span><br><span class="line">        <span class="type">FileChannel</span> <span class="variable">fileChannel</span> <span class="operator">=</span> fileInputStream.getChannel();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//创建缓冲区</span></span><br><span class="line">        <span class="type">ByteBuffer</span> <span class="variable">byteBuffer</span> <span class="operator">=</span> ByteBuffer.allocate((<span class="type">int</span>)file.length());</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//将通道的数据读入到 Buffer</span></span><br><span class="line">        fileChannel.read(byteBuffer);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//将 byteBuffer 的字节数据转成 String</span></span><br><span class="line">        System.out.println(<span class="keyword">new</span> <span class="title class_">String</span>(byteBuffer.array()));</span><br><span class="line">        fileInputStream.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="应用实例3-使用一个-Buffer-完成文件读取、写入"><a href="#应用实例3-使用一个-Buffer-完成文件读取、写入" class="headerlink" title="应用实例3 - 使用一个 Buffer 完成文件读取、写入"></a>应用实例3 - 使用一个 Buffer 完成文件读取、写入</h3><p>实例要求：</p>
<ol>
<li>使用 <code>FileChannel</code>（通道）和方法 <code>read、write</code>，完成文件的拷贝</li>
<li>拷贝一个文本文件 <code>1.txt</code>，放在项目下即可</li>
<li>代码演示</li>
</ol>
<img src="https://npm.elemecdn.com/youthlql@1.0.0/netty/introduction/chapter_001/0016.png"/>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.nio;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.FileInputStream;</span><br><span class="line"><span class="keyword">import</span> java.io.FileOutputStream;</span><br><span class="line"><span class="keyword">import</span> java.nio.ByteBuffer;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.FileChannel;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NIOFileChannel03</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line"></span><br><span class="line">        <span class="type">FileInputStream</span> <span class="variable">fileInputStream</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileInputStream</span>(<span class="string">&quot;1.txt&quot;</span>);</span><br><span class="line">        <span class="type">FileChannel</span> <span class="variable">fileChannel01</span> <span class="operator">=</span> fileInputStream.getChannel();</span><br><span class="line">        <span class="type">FileOutputStream</span> <span class="variable">fileOutputStream</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileOutputStream</span>(<span class="string">&quot;2.txt&quot;</span>);</span><br><span class="line">        <span class="type">FileChannel</span> <span class="variable">fileChannel02</span> <span class="operator">=</span> fileOutputStream.getChannel();</span><br><span class="line"></span><br><span class="line">        <span class="type">ByteBuffer</span> <span class="variable">byteBuffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">512</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>) &#123; <span class="comment">//循环读取</span></span><br><span class="line"></span><br><span class="line">            <span class="comment">//这里有一个重要的操作，一定不要忘了</span></span><br><span class="line">            <span class="comment">/*</span></span><br><span class="line"><span class="comment">            public final Buffer clear() &#123;</span></span><br><span class="line"><span class="comment">                position = 0;</span></span><br><span class="line"><span class="comment">                limit = capacity;</span></span><br><span class="line"><span class="comment">                mark = -1;</span></span><br><span class="line"><span class="comment">                return this;</span></span><br><span class="line"><span class="comment">            &#125;</span></span><br><span class="line"><span class="comment">            */</span></span><br><span class="line">            byteBuffer.clear(); <span class="comment">//清空 buffer</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">read</span> <span class="operator">=</span> fileChannel01.read(byteBuffer);</span><br><span class="line">            System.out.println(<span class="string">&quot;read = &quot;</span> + read);</span><br><span class="line">            <span class="keyword">if</span> (read == -<span class="number">1</span>) &#123; <span class="comment">//表示读完</span></span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">//将 buffer 中的数据写入到 fileChannel02--2.txt</span></span><br><span class="line">            byteBuffer.flip();</span><br><span class="line">            fileChannel02.write(byteBuffer);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//关闭相关的流</span></span><br><span class="line">        fileInputStream.close();</span><br><span class="line">        fileOutputStream.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="应用实例4-拷贝文件-transferFrom-方法"><a href="#应用实例4-拷贝文件-transferFrom-方法" class="headerlink" title="应用实例4 - 拷贝文件 transferFrom 方法"></a>应用实例4 - 拷贝文件 transferFrom 方法</h3><ol>
<li>实例要求：</li>
<li>使用 <code>FileChannel</code>（通道）和方法 <code>transferFrom</code>，完成文件的拷贝</li>
<li>拷贝一张图片</li>
<li>代码演示</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.nio;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.FileInputStream;</span><br><span class="line"><span class="keyword">import</span> java.io.FileOutputStream;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.FileChannel;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NIOFileChannel04</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//创建相关流</span></span><br><span class="line">        <span class="type">FileInputStream</span> <span class="variable">fileInputStream</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileInputStream</span>(<span class="string">&quot;d:\\a.jpg&quot;</span>);</span><br><span class="line">        <span class="type">FileOutputStream</span> <span class="variable">fileOutputStream</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileOutputStream</span>(<span class="string">&quot;d:\\a2.jpg&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//获取各个流对应的 FileChannel</span></span><br><span class="line">        <span class="type">FileChannel</span> <span class="variable">sourceCh</span> <span class="operator">=</span> fileInputStream.getChannel();</span><br><span class="line">        <span class="type">FileChannel</span> <span class="variable">destCh</span> <span class="operator">=</span> fileOutputStream.getChannel();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//使用 transferForm 完成拷贝</span></span><br><span class="line">        destCh.transferFrom(sourceCh, <span class="number">0</span>, sourceCh.size());</span><br><span class="line"></span><br><span class="line">        <span class="comment">//关闭相关通道和流</span></span><br><span class="line">        sourceCh.close();</span><br><span class="line">        destCh.close();</span><br><span class="line">        fileInputStream.close();</span><br><span class="line">        fileOutputStream.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="关于-Buffer-和-Channel-的注意事项和细节"><a href="#关于-Buffer-和-Channel-的注意事项和细节" class="headerlink" title="关于 Buffer 和 Channel 的注意事项和细节"></a>关于 Buffer 和 Channel 的注意事项和细节</h3><ol>
<li><code>ByteBuffer</code> 支持类型化的 <code>put</code> 和 <code>get</code>，<code>put</code> 放入的是什么数据类型，<code>get</code> 就应该使用相应的数据类型来取出，否则可能有 <code>BufferUnderflowException</code> 异常。【举例说明】</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.nio;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.nio.ByteBuffer;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NIOByteBufferPutGet</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//创建一个 Buffer</span></span><br><span class="line">        <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">64</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//类型化方式放入数据</span></span><br><span class="line">        buffer.putInt(<span class="number">100</span>);</span><br><span class="line">        buffer.putLong(<span class="number">9</span>);</span><br><span class="line">        buffer.putChar(<span class="string">&#x27;尚&#x27;</span>);</span><br><span class="line">        buffer.putShort((<span class="type">short</span>) <span class="number">4</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//取出</span></span><br><span class="line">        buffer.flip();</span><br><span class="line">        </span><br><span class="line">        System.out.println();</span><br><span class="line">        </span><br><span class="line">        System.out.println(buffer.getInt());</span><br><span class="line">        System.out.println(buffer.getLong());</span><br><span class="line">        System.out.println(buffer.getChar());</span><br><span class="line">        System.out.println(buffer.getShort());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ol start="2">
<li>可以将一个普通 <code>Buffer</code> 转成只读 <code>Buffer</code>【举例说明】</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.nio;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.nio.ByteBuffer;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ReadOnlyBuffer</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//创建一个 buffer</span></span><br><span class="line">        <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">64</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">64</span>; i++) &#123;</span><br><span class="line">            buffer.put((<span class="type">byte</span>) i);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//读取</span></span><br><span class="line">        buffer.flip();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//得到一个只读的 Buffer</span></span><br><span class="line">        <span class="type">ByteBuffer</span> <span class="variable">readOnlyBuffer</span> <span class="operator">=</span> buffer.asReadOnlyBuffer();</span><br><span class="line">        System.out.println(readOnlyBuffer.getClass());</span><br><span class="line"></span><br><span class="line">        <span class="comment">//读取</span></span><br><span class="line">        <span class="keyword">while</span> (readOnlyBuffer.hasRemaining()) &#123;</span><br><span class="line">            System.out.println(readOnlyBuffer.get());</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        readOnlyBuffer.put((<span class="type">byte</span>) <span class="number">100</span>); <span class="comment">//ReadOnlyBufferException</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ol start="3">
<li><code>NIO</code> 还提供了 <code>MappedByteBuffer</code>，可以让文件直接在内存（堆外的内存）中进行修改，而如何同步到文件由 <code>NIO</code> 来完成。【举例说明】</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.nio;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.RandomAccessFile;</span><br><span class="line"><span class="keyword">import</span> java.nio.MappedByteBuffer;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.FileChannel;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 说明 1.MappedByteBuffer 可让文件直接在内存（堆外内存）修改,操作系统不需要拷贝一次</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MappedByteBufferTest</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line"></span><br><span class="line">        <span class="type">RandomAccessFile</span> <span class="variable">randomAccessFile</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RandomAccessFile</span>(<span class="string">&quot;1.txt&quot;</span>, <span class="string">&quot;rw&quot;</span>);</span><br><span class="line">        <span class="comment">//获取对应的通道</span></span><br><span class="line">        <span class="type">FileChannel</span> <span class="variable">channel</span> <span class="operator">=</span> randomAccessFile.getChannel();</span><br><span class="line"></span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * 参数 1:FileChannel.MapMode.READ_WRITE 使用的读写模式</span></span><br><span class="line"><span class="comment">         * 参数 2：0：可以直接修改的起始位置</span></span><br><span class="line"><span class="comment">         * 参数 3:5: 是映射到内存的大小（不是索引位置），即将 1.txt 的多少个字节映射到内存</span></span><br><span class="line"><span class="comment">         * 可以直接修改的范围就是 0-5</span></span><br><span class="line"><span class="comment">         * 实际类型 DirectByteBuffer</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="type">MappedByteBuffer</span> <span class="variable">mappedByteBuffer</span> <span class="operator">=</span> channel.map(FileChannel.MapMode.READ_WRITE, <span class="number">0</span>, <span class="number">5</span>);</span><br><span class="line"></span><br><span class="line">        mappedByteBuffer.put(<span class="number">0</span>, (<span class="type">byte</span>) <span class="string">&#x27;H&#x27;</span>);</span><br><span class="line">        mappedByteBuffer.put(<span class="number">3</span>, (<span class="type">byte</span>) <span class="string">&#x27;9&#x27;</span>);</span><br><span class="line">        mappedByteBuffer.put(<span class="number">5</span>, (<span class="type">byte</span>) <span class="string">&#x27;Y&#x27;</span>);<span class="comment">//IndexOutOfBoundsException</span></span><br><span class="line"></span><br><span class="line">        randomAccessFile.close();</span><br><span class="line">        System.out.println(<span class="string">&quot;修改成功~~&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ol start="4">
<li>前面我们讲的读写操作，都是通过一个 <code>Buffer</code> 完成的，<code>NIO</code> 还支持通过多个 <code>Buffer</code>（即 <code>Buffer</code>数组）完成读写操作，即 <code>Scattering</code> 和 <code>Gathering</code>【举例说明】</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.nio;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.net.InetSocketAddress;</span><br><span class="line"><span class="keyword">import</span> java.nio.ByteBuffer;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.ServerSocketChannel;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.SocketChannel;</span><br><span class="line"><span class="keyword">import</span> java.util.Arrays;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Scattering：将数据写入到 buffer 时，可以采用 buffer 数组，依次写入 [分散]</span></span><br><span class="line"><span class="comment"> * Gathering：从 buffer 读取数据时，可以采用 buffer 数组，依次读</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ScatteringAndGatheringTest</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//使用 ServerSocketChannel 和 SocketChannel 网络</span></span><br><span class="line">        <span class="type">ServerSocketChannel</span> <span class="variable">serverSocketChannel</span> <span class="operator">=</span> ServerSocketChannel.open();</span><br><span class="line">        <span class="type">InetSocketAddress</span> <span class="variable">inetSocketAddress</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="number">7000</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//绑定端口到 socket，并启动</span></span><br><span class="line">        serverSocketChannel.socket().bind(inetSocketAddress);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//创建 buffer 数组</span></span><br><span class="line">        ByteBuffer[] byteBuffers = <span class="keyword">new</span> <span class="title class_">ByteBuffer</span>[<span class="number">2</span>];</span><br><span class="line">        byteBuffers[<span class="number">0</span>] = ByteBuffer.allocate(<span class="number">5</span>);</span><br><span class="line">        byteBuffers[<span class="number">1</span>] = ByteBuffer.allocate(<span class="number">3</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//等客户端连接 (telnet)</span></span><br><span class="line">        <span class="type">SocketChannel</span> <span class="variable">socketChannel</span> <span class="operator">=</span> serverSocketChannel.accept();</span><br><span class="line"></span><br><span class="line">        <span class="type">int</span> <span class="variable">messageLength</span> <span class="operator">=</span> <span class="number">8</span>; <span class="comment">//假定从客户端接收 8 个字节</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">//循环的读取</span></span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">byteRead</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">while</span> (byteRead &lt; messageLength) &#123;</span><br><span class="line">                <span class="type">long</span> <span class="variable">l</span> <span class="operator">=</span> socketChannel.read(byteBuffers);</span><br><span class="line">                byteRead += l; <span class="comment">//累计读取的字节数</span></span><br><span class="line">                System.out.println(<span class="string">&quot;byteRead = &quot;</span> + byteRead);</span><br><span class="line">                <span class="comment">//使用流打印,看看当前的这个 buffer 的 position 和 limit</span></span><br><span class="line">                Arrays.asList(byteBuffers).stream().map(buffer -&gt; <span class="string">&quot;position = &quot;</span> + buffer.position() + <span class="string">&quot;, limit = &quot;</span> + buffer.limit()).forEach(System.out::println);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">//将所有的 buffer 进行 flip</span></span><br><span class="line">            Arrays.asList(byteBuffers).forEach(buffer -&gt; buffer.flip());</span><br><span class="line">            <span class="comment">//将数据读出显示到客户端</span></span><br><span class="line">            <span class="type">long</span> <span class="variable">byteWirte</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">            <span class="keyword">while</span> (byteWirte &lt; messageLength) &#123;</span><br><span class="line">                <span class="type">long</span> <span class="variable">l</span> <span class="operator">=</span> socketChannel.write(byteBuffers);<span class="comment">//</span></span><br><span class="line">                byteWirte += l;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">//将所有的buffer进行clear</span></span><br><span class="line">            Arrays.asList(byteBuffers).forEach(buffer -&gt; &#123;</span><br><span class="line">                buffer.clear();</span><br><span class="line">            &#125;);</span><br><span class="line">            </span><br><span class="line">            System.out.println(<span class="string">&quot;byteRead = &quot;</span> + byteRead + <span class="string">&quot;, byteWrite = &quot;</span> + byteWirte + <span class="string">&quot;, messagelength = &quot;</span> + messageLength);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="Selector（选择器）"><a href="#Selector（选择器）" class="headerlink" title="Selector（选择器）"></a>Selector（选择器）</h2><h3 id="基本介绍-2"><a href="#基本介绍-2" class="headerlink" title="基本介绍"></a>基本介绍</h3><ol>
<li><code>Java</code> 的 <code>NIO</code>，用非阻塞的 <code>IO</code> 方式。可以用一个线程，处理多个的客户端连接，就会使用到 <code>Selector</code>（选择器）。</li>
<li><code>Selector</code> 能够检测多个注册的通道上是否有事件发生（注意：多个 <code>Channel</code> 以事件的方式可以注册到同一个 <code>Selector</code>），如果有事件发生，便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道，也就是管理多个连接和请求。</li>
<li>只有在连接&#x2F;通道真正有读写事件发生时，才会进行读写，就大大地减少了系统开销，并且不必为每个连接都创建一个线程，不用去维护多个线程。</li>
<li>避免了多线程之间的上下文切换导致的开销。</li>
</ol>
<h3 id="Selector-示意图和特点说明"><a href="#Selector-示意图和特点说明" class="headerlink" title="Selector 示意图和特点说明"></a>Selector 示意图和特点说明</h3><img src="https://npm.elemecdn.com/youthlql@1.0.0/netty/introduction/chapter_001/0017.png"/>

<p>说明如下：</p>
<ol>
<li><code>Netty</code> 的 <code>IO</code> 线程 <code>NioEventLoop</code> 聚合了 <code>Selector</code>（选择器，也叫多路复用器），可以同时并发处理成百上千个客户端连接。</li>
<li>当线程从某客户端 <code>Socket</code> 通道进行读写数据时，若没有数据可用时，该线程可以进行其他任务。</li>
<li>线程通常将非阻塞 <code>IO</code> 的空闲时间用于在其他通道上执行 <code>IO</code> 操作，所以单独的线程可以管理多个输入和输出通道。</li>
<li>由于读写操作都是非阻塞的，这就可以充分提升 <code>IO</code> 线程的运行效率，避免由于频繁 <code>I/O</code> 阻塞导致的线程挂起。</li>
<li>一个 <code>I/O</code> 线程可以并发处理 <code>N</code> 个客户端连接和读写操作，这从根本上解决了传统同步阻塞 <code>I/O</code> 一连接一线程模型，架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。</li>
</ol>
<h3 id="Selector-类相关方法"><a href="#Selector-类相关方法" class="headerlink" title="Selector 类相关方法"></a>Selector 类相关方法</h3><img src="https://npm.elemecdn.com/youthlql@1.0.0/netty/introduction/chapter_001/0018.png"/>

<h3 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h3><ol>
<li><code>NIO</code> 中的 <code>ServerSocketChannel</code> 功能类似 <code>ServerSocket</code>、<code>SocketChannel</code> 功能类似 <code>Socket</code>。</li>
<li><code>Selector</code> 相关方法说明<ul>
<li><code>selector.select();</code> &#x2F;&#x2F;阻塞</li>
<li><code>selector.select(1000);</code> &#x2F;&#x2F;阻塞 1000 毫秒，在 1000 毫秒后返回</li>
<li><code>selector.wakeup();</code> &#x2F;&#x2F;唤醒 selector</li>
<li><code>selector.selectNow();</code> &#x2F;&#x2F;不阻塞，立马返还</li>
</ul>
</li>
</ol>
<h2 id="NIO-非阻塞网络编程原理分析图"><a href="#NIO-非阻塞网络编程原理分析图" class="headerlink" title="NIO 非阻塞网络编程原理分析图"></a>NIO 非阻塞网络编程原理分析图</h2><p><code>NIO</code> 非阻塞网络编程相关的（<code>Selector</code>、<code>SelectionKey</code>、<code>ServerScoketChannel</code> 和 <code>SocketChannel</code>）关系梳理图</p>
<img src="https://npm.elemecdn.com/youthlql@1.0.0/netty/introduction/chapter_001/0019.png"/>

<p>对上图的说明：</p>
<ol>
<li>当客户端连接时，会通过 <code>ServerSocketChannel</code> 得到 <code>SocketChannel</code>。</li>
<li><code>Selector</code> 进行监听 <code>select</code> 方法，返回有事件发生的通道的个数。</li>
<li>将 <code>socketChannel</code> 注册到 <code>Selector</code> 上，<code>register(Selector sel, int ops)</code>，一个 <code>Selector</code> 上可以注册多个 <code>SocketChannel</code>。</li>
<li>注册后返回一个 <code>SelectionKey</code>，会和该 <code>Selector</code> 关联（集合）。</li>
<li>进一步得到各个 <code>SelectionKey</code>（有事件发生）。</li>
<li>在通过 <code>SelectionKey</code> 反向获取 <code>SocketChannel</code>，方法 <code>channel()</code>。</li>
<li>可以通过得到的 <code>channel</code>，完成业务处理。</li>
<li>直接看后面代码吧</li>
</ol>
<h2 id="NIO-非阻塞网络编程快速入门"><a href="#NIO-非阻塞网络编程快速入门" class="headerlink" title="NIO 非阻塞网络编程快速入门"></a>NIO 非阻塞网络编程快速入门</h2><p>案例：</p>
<ol>
<li>编写一个 <code>NIO</code> 入门案例，实现服务器端和客户端之间的数据简单通讯（非阻塞）</li>
<li>目的：理解 <code>NIO</code> 非阻塞网络编程机制</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.nio;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.net.InetSocketAddress;</span><br><span class="line"><span class="keyword">import</span> java.nio.ByteBuffer;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.*;</span><br><span class="line"><span class="keyword">import</span> java.util.Iterator;</span><br><span class="line"><span class="keyword">import</span> java.util.Set;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NIOServer</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception&#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//创建ServerSocketChannel -&gt; ServerSocket</span></span><br><span class="line"></span><br><span class="line">        <span class="type">ServerSocketChannel</span> <span class="variable">serverSocketChannel</span> <span class="operator">=</span> ServerSocketChannel.open();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//得到一个Selecor对象</span></span><br><span class="line">        <span class="type">Selector</span> <span class="variable">selector</span> <span class="operator">=</span> Selector.open();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//绑定一个端口6666, 在服务器端监听</span></span><br><span class="line">        serverSocketChannel.socket().bind(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="number">6666</span>));</span><br><span class="line">        <span class="comment">//设置为非阻塞</span></span><br><span class="line">        serverSocketChannel.configureBlocking(<span class="literal">false</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//把 serverSocketChannel 注册到  selector 关心 事件为 OP_ACCEPT       pos_1</span></span><br><span class="line">        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);</span><br><span class="line"></span><br><span class="line">        System.out.println(<span class="string">&quot;注册后的selectionkey 数量=&quot;</span> + selector.keys().size()); <span class="comment">// 1</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        <span class="comment">//循环等待客户端连接</span></span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line"></span><br><span class="line">            <span class="comment">//这里我们等待1秒，如果没有事件发生, 返回</span></span><br><span class="line">            <span class="keyword">if</span>(selector.select(<span class="number">1000</span>) == <span class="number">0</span>) &#123; <span class="comment">//没有事件发生</span></span><br><span class="line">                System.out.println(<span class="string">&quot;服务器等待了1秒，无连接&quot;</span>);</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">//如果返回的&gt;0, 就获取到相关的 selectionKey集合</span></span><br><span class="line">            <span class="comment">//1.如果返回的&gt;0， 表示已经获取到关注的事件</span></span><br><span class="line">            <span class="comment">//2. selector.selectedKeys() 返回关注事件的集合</span></span><br><span class="line">            <span class="comment">//   通过 selectionKeys 反向获取通道</span></span><br><span class="line">            Set&lt;SelectionKey&gt; selectionKeys = selector.selectedKeys();</span><br><span class="line">            System.out.println(<span class="string">&quot;selectionKeys 数量 = &quot;</span> + selectionKeys.size());</span><br><span class="line"></span><br><span class="line">            <span class="comment">//遍历 Set&lt;SelectionKey&gt;, 使用迭代器遍历</span></span><br><span class="line">            Iterator&lt;SelectionKey&gt; keyIterator = selectionKeys.iterator();</span><br><span class="line"></span><br><span class="line">            <span class="keyword">while</span> (keyIterator.hasNext()) &#123;</span><br><span class="line">                <span class="comment">//获取到SelectionKey</span></span><br><span class="line">                <span class="type">SelectionKey</span> <span class="variable">key</span> <span class="operator">=</span> keyIterator.next();</span><br><span class="line">                <span class="comment">//根据key 对应的通道发生的事件做相应处理</span></span><br><span class="line">                <span class="keyword">if</span>(key.isAcceptable()) &#123; <span class="comment">//如果是 OP_ACCEPT, 有新的客户端连接</span></span><br><span class="line">                    <span class="comment">//该该客户端生成一个 SocketChannel</span></span><br><span class="line">                    <span class="type">SocketChannel</span> <span class="variable">socketChannel</span> <span class="operator">=</span> serverSocketChannel.accept();</span><br><span class="line">                    System.out.println(<span class="string">&quot;客户端连接成功 生成了一个 socketChannel &quot;</span> + socketChannel.hashCode());</span><br><span class="line">                    <span class="comment">//将  SocketChannel 设置为非阻塞</span></span><br><span class="line">                    socketChannel.configureBlocking(<span class="literal">false</span>);</span><br><span class="line">                    <span class="comment">//将socketChannel 注册到selector, 关注事件为 OP_READ， 同时给socketChannel</span></span><br><span class="line">                    <span class="comment">//关联一个Buffer</span></span><br><span class="line">                    socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(<span class="number">1024</span>));</span><br><span class="line"></span><br><span class="line">                    System.out.println(<span class="string">&quot;客户端连接后 ，注册的selectionkey 数量=&quot;</span> + selector.keys().size()); <span class="comment">//2,3,4..</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span>(key.isReadable()) &#123;  <span class="comment">//发生 OP_READ</span></span><br><span class="line"></span><br><span class="line">                    <span class="comment">//通过key 反向获取到对应channel</span></span><br><span class="line">                    <span class="type">SocketChannel</span> <span class="variable">channel</span> <span class="operator">=</span> (SocketChannel)key.channel();</span><br><span class="line"></span><br><span class="line">                    <span class="comment">//获取到该channel关联的buffer</span></span><br><span class="line">                    <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> (ByteBuffer)key.attachment();</span><br><span class="line">                    channel.read(buffer);</span><br><span class="line">                    System.out.println(<span class="string">&quot;form 客户端 &quot;</span> + <span class="keyword">new</span> <span class="title class_">String</span>(buffer.array()));</span><br><span class="line"></span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="comment">//手动从集合中移动当前的selectionKey, 防止重复操作</span></span><br><span class="line">                keyIterator.remove();</span><br><span class="line"></span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>




<blockquote>
<p>pos1：</p>
<p>1、对操作系统有一定了解的同学，就会大概知道这里监听的是一个Accept通道。这个通道的<br>作用就是监听，实际建立连接了还会有一个通道。<br>2、简单说一下为什么。因为客户端发请求的时候，服务器这边是肯定要先有一个监听通道，<br>监听某个端口是否有客户端要建立链接，如果有客户端想要建立链接，那么会再创建一个和<br>客户端真正通信的通道。<br>3、如果有其它客户端还想要建立链接，这个Accept监听端口监听到了，就会再创建几个真正<br>的通信通道。<br>4、也就是Server的一个端口可以建立多个TCP连接，因为IP层协议通过<br>目标地址+端口+源地址+源端口四个信息识别一个上下文</p>
<p><strong>顺便插一句嘴：因为学netty的过程中，发现计算机网络和操作系统蛮重要的，所以接下来会写几篇这方面的文章</strong></p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.nio;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.net.InetSocketAddress;</span><br><span class="line"><span class="keyword">import</span> java.nio.ByteBuffer;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.SocketChannel;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NIOClient</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception&#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//得到一个网络通道</span></span><br><span class="line">        <span class="type">SocketChannel</span> <span class="variable">socketChannel</span> <span class="operator">=</span> SocketChannel.open();</span><br><span class="line">        <span class="comment">//设置非阻塞</span></span><br><span class="line">        socketChannel.configureBlocking(<span class="literal">false</span>);</span><br><span class="line">        <span class="comment">//提供服务器端的ip 和 端口</span></span><br><span class="line">        <span class="type">InetSocketAddress</span> <span class="variable">inetSocketAddress</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="string">&quot;127.0.0.1&quot;</span>, <span class="number">6666</span>);</span><br><span class="line">        <span class="comment">//连接服务器</span></span><br><span class="line">        <span class="keyword">if</span> (!socketChannel.connect(inetSocketAddress)) &#123;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">while</span> (!socketChannel.finishConnect()) &#123;</span><br><span class="line">                System.out.println(<span class="string">&quot;因为连接需要时间，客户端不会阻塞，可以做其它工作..&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//...如果连接成功，就发送数据</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> <span class="string">&quot;hello, 尚硅谷~&quot;</span>;</span><br><span class="line">        <span class="comment">//Wraps a byte array into a buffer</span></span><br><span class="line">        <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.wrap(str.getBytes());</span><br><span class="line">        <span class="comment">//发送数据，将 buffer 数据写入 channel</span></span><br><span class="line">        socketChannel.write(buffer);</span><br><span class="line">        System.in.read();</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>实际执行效果可以复制代码去试下</p>
<h3 id="SelectionKey"><a href="#SelectionKey" class="headerlink" title="SelectionKey"></a>SelectionKey</h3><ol>
<li><code>SelectionKey</code>，表示 <code>Selector</code> 和网络通道的注册关系，共四种：<ul>
<li><code>int OP_ACCEPT</code>：有新的网络连接可以 <code>accept</code>，值为 <code>16</code></li>
<li><code>int OP_CONNECT</code>：代表连接已经建立，值为 <code>8</code></li>
<li><code>int OP_READ</code>：代表读操作，值为 <code>1</code></li>
<li><code>int OP_WRITE</code>：代表写操作，值为 <code>4</code></li>
</ul>
</li>
</ol>
<p>源码中：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">OP_READ</span> <span class="operator">=</span> <span class="number">1</span> &lt;&lt; <span class="number">0</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">OP_WRITE</span> <span class="operator">=</span> <span class="number">1</span> &lt;&lt; <span class="number">2</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">OP_CONNECT</span> <span class="operator">=</span> <span class="number">1</span> &lt;&lt; <span class="number">3</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">OP_ACCEPT</span> <span class="operator">=</span> <span class="number">1</span> &lt;&lt; <span class="number">4</span>;</span><br></pre></td></tr></table></figure>

<ol start="2">
<li><code>SelectionKey</code> 相关方法</li>
</ol>
<img src="https://npm.elemecdn.com/youthlql@1.0.0/netty/introduction/chapter_001/0020.png"/>

<h3 id="ServerSocketChannel"><a href="#ServerSocketChannel" class="headerlink" title="ServerSocketChannel"></a>ServerSocketChannel</h3><ol>
<li><code>ServerSocketChannel</code> 在服务器端监听新的客户端 <code>Socket</code> 连接，负责监听，不负责实际的读写操作</li>
<li>相关方法如下</li>
</ol>
<img src="https://npm.elemecdn.com/youthlql@1.0.0/netty/introduction/chapter_001/0021.png"/>

<h3 id="SocketChannel"><a href="#SocketChannel" class="headerlink" title="SocketChannel"></a>SocketChannel</h3><ol>
<li><code>SocketChannel</code>，网络 <code>IO</code> 通道，<strong>具体负责进行读写操作</strong>。<code>NIO</code> 把缓冲区的数据写入通道，或者把通道里的数据读到缓冲区。</li>
<li>相关方法如下</li>
</ol>
<img src="https://npm.elemecdn.com/youthlql@1.0.0/netty/introduction/chapter_001/0022.png"/>

<h2 id="NIO网络编程应用实例-群聊系统"><a href="#NIO网络编程应用实例-群聊系统" class="headerlink" title="NIO网络编程应用实例 - 群聊系统"></a>NIO网络编程应用实例 - 群聊系统</h2><p>实例要求：</p>
<ol>
<li>编写一个 <code>NIO</code> 群聊系统，实现服务器端和客户端之间的数据简单通讯（非阻塞）</li>
<li>实现多人群聊</li>
<li>服务器端：可以监测用户上线，离线，并实现消息转发功能</li>
<li>客户端：通过 <code>Channel</code> 可以无阻塞发送消息给其它所有用户，同时可以接受其它用户发送的消息（有服务器转发得到）</li>
<li>目的：进一步理解 <code>NIO</code> 非阻塞网络编程机制</li>
<li>示意图分析和代码</li>
</ol>
<img src="https://npm.elemecdn.com/youthlql@1.0.0/netty/introduction/chapter_001/0023.png"/>

<p>代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// 服务端：</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">package</span> com.atguigu.nio.groupchat;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.net.InetSocketAddress;</span><br><span class="line"><span class="keyword">import</span> java.nio.ByteBuffer;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.Channel;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.SelectionKey;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.Selector;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.ServerSocketChannel;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.SocketChannel;</span><br><span class="line"><span class="keyword">import</span> java.util.Iterator;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GroupChatServer</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//定义属性</span></span><br><span class="line">    <span class="keyword">private</span> Selector selector;</span><br><span class="line">    <span class="keyword">private</span> ServerSocketChannel listenChannel;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">PORT</span> <span class="operator">=</span> <span class="number">6667</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//构造器</span></span><br><span class="line">    <span class="comment">//初始化工作</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">GroupChatServer</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">//得到选择器</span></span><br><span class="line">            selector = Selector.open();</span><br><span class="line">            <span class="comment">//ServerSocketChannel</span></span><br><span class="line">            listenChannel = ServerSocketChannel.open();</span><br><span class="line">            <span class="comment">//绑定端口</span></span><br><span class="line">            listenChannel.socket().bind(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(PORT));</span><br><span class="line">            <span class="comment">//设置非阻塞模式</span></span><br><span class="line">            listenChannel.configureBlocking(<span class="literal">false</span>);</span><br><span class="line">            <span class="comment">//将该 listenChannel 注册到 selector</span></span><br><span class="line">            listenChannel.register(selector, SelectionKey.OP_ACCEPT);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">listen</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">//循环处理</span></span><br><span class="line">            <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> selector.select();</span><br><span class="line">                <span class="keyword">if</span> (count &gt; <span class="number">0</span>) &#123; <span class="comment">//有事件处理</span></span><br><span class="line">                    <span class="comment">// 遍历得到 selectionKey 集合</span></span><br><span class="line">                    Iterator&lt;SelectionKey&gt; iterator = selector.selectedKeys().iterator();</span><br><span class="line">                    <span class="keyword">while</span> (iterator.hasNext()) &#123;</span><br><span class="line">                        <span class="comment">//取出 selectionkey</span></span><br><span class="line">                        <span class="type">SelectionKey</span> <span class="variable">key</span> <span class="operator">=</span> iterator.next();</span><br><span class="line">                        <span class="comment">//监听到 accept</span></span><br><span class="line">                        <span class="keyword">if</span> (key.isAcceptable()) &#123;</span><br><span class="line">                            <span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> listenChannel.accept();</span><br><span class="line">                            sc.configureBlocking(<span class="literal">false</span>);</span><br><span class="line">                            <span class="comment">//将该 sc 注册到 seletor</span></span><br><span class="line">                            sc.register(selector, SelectionKey.OP_READ);</span><br><span class="line">                            <span class="comment">//提示</span></span><br><span class="line">                            System.out.println(sc.getRemoteAddress() + <span class="string">&quot; 上线 &quot;</span>);</span><br><span class="line">                        &#125;</span><br><span class="line">                        <span class="keyword">if</span> (key.isReadable()) &#123;<span class="comment">//通道发送read事件，即通道是可读的状态</span></span><br><span class="line">                            <span class="comment">// 处理读(专门写方法..)</span></span><br><span class="line">                            readData(key);</span><br><span class="line">                        &#125;</span><br><span class="line">                        <span class="comment">//当前的 key 删除，防止重复处理</span></span><br><span class="line">                        iterator.remove();</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    System.out.println(<span class="string">&quot;等待....&quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="comment">//发生异常处理....</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//读取客户端消息</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">readData</span><span class="params">(SelectionKey key)</span> &#123;</span><br><span class="line">        <span class="type">SocketChannel</span> <span class="variable">channel</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">//得到 channel</span></span><br><span class="line">            channel = (SocketChannel) key.channel();</span><br><span class="line">            <span class="comment">//创建 buffer</span></span><br><span class="line">            <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">1024</span>);</span><br><span class="line">            <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> channel.read(buffer);</span><br><span class="line">            <span class="comment">//根据 count 的值做处理</span></span><br><span class="line">            <span class="keyword">if</span> (count &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="comment">//把缓存区的数据转成字符串</span></span><br><span class="line">                <span class="type">String</span> <span class="variable">msg</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(buffer.array());</span><br><span class="line">                <span class="comment">//输出该消息</span></span><br><span class="line">                System.out.println(<span class="string">&quot;form客户端:&quot;</span> + msg);</span><br><span class="line">                <span class="comment">//向其它的客户端转发消息(去掉自己),专门写一个方法来处理</span></span><br><span class="line">                sendInfoToOtherClients(msg, channel);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                System.out.println(channel.getRemoteAddress() + <span class="string">&quot;离线了..&quot;</span>);</span><br><span class="line">                <span class="comment">//取消注册</span></span><br><span class="line">                key.cancel();</span><br><span class="line">                <span class="comment">//关闭通道</span></span><br><span class="line">                channel.close();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (IOException e2) &#123;</span><br><span class="line">                e2.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//转发消息给其它客户(通道)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">sendInfoToOtherClients</span><span class="params">(String msg, SocketChannel self)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line"></span><br><span class="line">        System.out.println(<span class="string">&quot;服务器转发消息中...&quot;</span>);</span><br><span class="line">        <span class="comment">//遍历所有注册到 selector 上的 SocketChannel,并排除 self</span></span><br><span class="line">        <span class="keyword">for</span> (SelectionKey key : selector.keys()) &#123;</span><br><span class="line">            <span class="comment">//通过 key 取出对应的 SocketChannel</span></span><br><span class="line">            <span class="type">Channel</span> <span class="variable">targetChannel</span> <span class="operator">=</span> key.channel();</span><br><span class="line">            <span class="comment">//排除自己</span></span><br><span class="line">            <span class="keyword">if</span> (targetChannel <span class="keyword">instanceof</span> SocketChannel &amp;&amp; targetChannel != self) &#123;</span><br><span class="line">                <span class="comment">//转型</span></span><br><span class="line">                <span class="type">SocketChannel</span> <span class="variable">dest</span> <span class="operator">=</span> (SocketChannel) targetChannel;</span><br><span class="line">                <span class="comment">//将 msg 存储到 buffer</span></span><br><span class="line">                <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.wrap(msg.getBytes());</span><br><span class="line">                <span class="comment">//将 buffer 的数据写入通道</span></span><br><span class="line">                dest.write(buffer);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">//创建服务器对象</span></span><br><span class="line">        <span class="type">GroupChatServer</span> <span class="variable">groupChatServer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">GroupChatServer</span>();</span><br><span class="line">        groupChatServer.listen();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 客户端：</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">package</span> com.atguigu.nio.groupchat;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.net.InetSocketAddress;</span><br><span class="line"><span class="keyword">import</span> java.nio.ByteBuffer;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.SelectionKey;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.Selector;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.SocketChannel;</span><br><span class="line"><span class="keyword">import</span> java.util.Iterator;</span><br><span class="line"><span class="keyword">import</span> java.util.Scanner;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GroupChatClient</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//定义相关的属性</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">HOST</span> <span class="operator">=</span> <span class="string">&quot;127.0.0.1&quot;</span>;<span class="comment">//服务器的ip</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">PORT</span> <span class="operator">=</span> <span class="number">6667</span>;<span class="comment">//服务器端口</span></span><br><span class="line">    <span class="keyword">private</span> Selector selector;</span><br><span class="line">    <span class="keyword">private</span> SocketChannel socketChannel;</span><br><span class="line">    <span class="keyword">private</span> String username;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//构造器,完成初始化工作</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">GroupChatClient</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        </span><br><span class="line">        selector = Selector.open();</span><br><span class="line">        <span class="comment">//连接服务器</span></span><br><span class="line">        socketChannel = SocketChannel.open(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(HOST, PORT));</span><br><span class="line">        <span class="comment">//设置非阻塞</span></span><br><span class="line">        socketChannel.configureBlocking(<span class="literal">false</span>);</span><br><span class="line">        <span class="comment">//将 channel 注册到selector</span></span><br><span class="line">        socketChannel.register(selector, SelectionKey.OP_READ);</span><br><span class="line">        <span class="comment">//得到 username</span></span><br><span class="line">        username = socketChannel.getLocalAddress().toString().substring(<span class="number">1</span>);</span><br><span class="line">        System.out.println(username + <span class="string">&quot; is ok...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//向服务器发送消息</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sendInfo</span><span class="params">(String info)</span> &#123;</span><br><span class="line">        info = username + <span class="string">&quot; 说：&quot;</span> + info;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            socketChannel.write(ByteBuffer.wrap(info.getBytes()));</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//读取从服务器端回复的消息</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">readInfo</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">readChannels</span> <span class="operator">=</span> selector.select();</span><br><span class="line">            <span class="keyword">if</span> (readChannels &gt; <span class="number">0</span>) &#123;<span class="comment">//有可以用的通道</span></span><br><span class="line">                Iterator&lt;SelectionKey&gt; iterator = selector.selectedKeys().iterator();</span><br><span class="line">                <span class="keyword">while</span> (iterator.hasNext()) &#123;</span><br><span class="line">                    <span class="type">SelectionKey</span> <span class="variable">key</span> <span class="operator">=</span> iterator.next();</span><br><span class="line">                    <span class="keyword">if</span> (key.isReadable()) &#123;</span><br><span class="line">                        <span class="comment">//得到相关的通道</span></span><br><span class="line">                        <span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> (SocketChannel) key.channel();</span><br><span class="line">                        <span class="comment">//得到一个 Buffer</span></span><br><span class="line">                        <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">1024</span>);</span><br><span class="line">                        <span class="comment">//读取</span></span><br><span class="line">                        sc.read(buffer);</span><br><span class="line">                        <span class="comment">//把读到的缓冲区的数据转成字符串</span></span><br><span class="line">                        <span class="type">String</span> <span class="variable">msg</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(buffer.array());</span><br><span class="line">                        System.out.println(msg.trim());</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">                iterator.remove(); <span class="comment">//删除当前的 selectionKey,防止重复操作</span></span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">//System.out.println(&quot;没有可以用的通道...&quot;);</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//启动我们客户端</span></span><br><span class="line">        <span class="type">GroupChatClient</span> <span class="variable">chatClient</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">GroupChatClient</span>();</span><br><span class="line">        <span class="comment">//启动一个线程,每个 3 秒，读取从服务器发送数据</span></span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Thread</span>() &#123;</span><br><span class="line">            <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">                <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">                    chatClient.readInfo();</span><br><span class="line">                    <span class="keyword">try</span> &#123;</span><br><span class="line">                        Thread.currentThread().sleep(<span class="number">3000</span>);</span><br><span class="line">                    &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                        e.printStackTrace();</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;.start();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//发送数据给服务器端</span></span><br><span class="line">        <span class="type">Scanner</span> <span class="variable">scanner</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Scanner</span>(System.in);</span><br><span class="line">        <span class="keyword">while</span> (scanner.hasNextLine()) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> scanner.nextLine();</span><br><span class="line">            chatClient.sendInfo(s);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="NIO与零拷贝"><a href="#NIO与零拷贝" class="headerlink" title="NIO与零拷贝"></a>NIO与零拷贝</h2><blockquote>
<p>1、尚硅谷这里的零拷贝感觉讲的感觉有点问题，但是为了笔记的完整性，任然保留了这里的笔记。不过笔者考虑再写一篇零拷贝。</p>
<p>2、而且这里课件的图也看不太清</p>
<p>3、读者可以将我写的零拷贝和尚硅谷这里讲的零拷贝对照着看，取长补短</p>
</blockquote>
<h3 id="零拷贝基本介绍"><a href="#零拷贝基本介绍" class="headerlink" title="零拷贝基本介绍"></a>零拷贝基本介绍</h3><ol>
<li>零拷贝是网络编程的关键，很多性能优化都离不开。</li>
<li>在 <code>Java</code> 程序中，常用的零拷贝有 <code>mmap</code>（内存映射）和 <code>sendFile</code>。那么，他们在 <code>OS</code> 里，到底是怎么样的一个的设计？我们分析 <code>mmap</code> 和 <code>sendFile</code> 这两个零拷贝</li>
<li>另外我们看下 <code>NIO</code> 中如何使用零拷贝</li>
</ol>
<h3 id="传统-IO-数据读写"><a href="#传统-IO-数据读写" class="headerlink" title="传统 IO 数据读写"></a>传统 IO 数据读写</h3><p><code>Java</code> 传统 <code>IO</code> 和网络编程的一段代码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">File</span> <span class="variable">file</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">File</span>(<span class="string">&quot;test.txt&quot;</span>);</span><br><span class="line"><span class="type">RandomAccessFile</span> <span class="variable">raf</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RandomAccessFile</span>(file, <span class="string">&quot;rw&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="type">byte</span>[] arr = <span class="keyword">new</span> <span class="title class_">byte</span>[(<span class="type">int</span>) file.length()];</span><br><span class="line">raf.read(arr);</span><br><span class="line"></span><br><span class="line"><span class="type">Socket</span> <span class="variable">socket</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServerSocket</span>(<span class="number">8080</span>).accept();</span><br><span class="line">socket.getOutputStream().write(arr);</span><br></pre></td></tr></table></figure>

<h3 id="传统-IO-模型"><a href="#传统-IO-模型" class="headerlink" title="传统 IO 模型"></a>传统 IO 模型</h3><img src="https://npm.elemecdn.com/youthlql@1.0.0/netty/introduction/chapter_001/0024.png"/>

<p><strong>DMA</strong>：<code>direct memory access</code> 直接内存拷贝（不使用 <code>CPU</code>）</p>
<h3 id="mmap-优化"><a href="#mmap-优化" class="headerlink" title="mmap 优化"></a>mmap 优化</h3><ol>
<li><code>mmap</code> 通过内存映射，将文件映射到内核缓冲区，同时，用户空间可以共享内核空间的数据。这样，在进行网络传输时，就可以减少内核空间到用户空间的拷贝次数。如下图</li>
<li><code>mmap</code> 示意图</li>
</ol>
<img src="https://npm.elemecdn.com/youthlql@1.0.0/netty/introduction/chapter_001/0025.png"/>

<h3 id="sendFile-优化"><a href="#sendFile-优化" class="headerlink" title="sendFile 优化"></a>sendFile 优化</h3><ol>
<li><code>Linux2.1</code> 版本提供了 <code>sendFile</code> 函数，其基本原理如下：数据根本不经过用户态，直接从内核缓冲区进入到 <code>SocketBuffer</code>，同时，由于和用户态完全无关，就减少了一次上下文切换</li>
<li>示意图和小结</li>
</ol>
<img src="https://npm.elemecdn.com/youthlql@1.0.0/netty/introduction/chapter_001/0026.png"/>

<ol start="3">
<li>提示：零拷贝从操作系统角度，是没有 <code>cpu</code> 拷贝</li>
<li><code>Linux在2.4</code> 版本中，做了一些修改，避免了从内核缓冲区拷贝到 <code>Socketbuffer</code> 的操作，直接拷贝到协议栈，从而再一次减少了数据拷贝。具体如下图和小结：</li>
</ol>
<img src="https://npm.elemecdn.com/youthlql@1.0.0/netty/introduction/chapter_001/0027.png"/>

<ol start="5">
<li>这里其实有一次 <code>cpu</code> 拷贝 <code>kernel buffer</code> -&gt; <code>socket buffer</code> 但是，拷贝的信息很少，比如 <code>lenght</code>、<code>offset</code> 消耗低，可以忽略</li>
</ol>
<h3 id="零拷贝的再次理解"><a href="#零拷贝的再次理解" class="headerlink" title="零拷贝的再次理解"></a>零拷贝的再次理解</h3><ol>
<li>我们说零拷贝，是从操作系统的角度来说的。因为内核缓冲区之间，没有数据是重复的（只有 <code>kernel buffer</code> 有一份数据）。</li>
<li>零拷贝不仅仅带来更少的数据复制，还能带来其他的性能优势，例如更少的上下文切换，更少的 <code>CPU</code> 缓存伪共享以及无 <code>CPU</code> 校验和计算。</li>
</ol>
<h3 id="mmap-和-sendFile-的区别"><a href="#mmap-和-sendFile-的区别" class="headerlink" title="mmap 和 sendFile 的区别"></a>mmap 和 sendFile 的区别</h3><ol>
<li><code>mmap</code> 适合小数据量读写，<code>sendFile</code> 适合大文件传输。</li>
<li><code>mmap</code> 需要 <code>4</code> 次上下文切换，<code>3</code> 次数据拷贝；<code>sendFile</code> 需要 <code>3</code> 次上下文切换，最少 <code>2</code> 次数据拷贝。</li>
<li><code>sendFile</code> 可以利用 <code>DMA</code> 方式，减少 <code>CPU</code> 拷贝，<code>mmap</code> 则不能（必须从内核拷贝到 <code>Socket</code>缓冲区）。</li>
</ol>
<h3 id="NIO-零拷贝案例"><a href="#NIO-零拷贝案例" class="headerlink" title="NIO 零拷贝案例"></a>NIO 零拷贝案例</h3><p>案例要求：</p>
<ol>
<li>使用传统的 <code>IO</code> 方法传递一个大文件</li>
<li>使用 <code>NIO</code> 零拷贝方式传递（<code>transferTo</code>）一个大文件</li>
<li>看看两种传递方式耗时时间分别是多少</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br></pre></td><td class="code"><pre><span class="line">NewIOServer.java</span><br><span class="line"></span><br><span class="line"><span class="keyword">package</span> com.atguigu.nio.zerocopy;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.net.InetSocketAddress;</span><br><span class="line"><span class="keyword">import</span> java.net.ServerSocket;</span><br><span class="line"><span class="keyword">import</span> java.nio.ByteBuffer;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.ServerSocketChannel;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.SocketChannel;</span><br><span class="line"></span><br><span class="line"><span class="comment">//服务器</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NewIOServer</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">InetSocketAddress</span> <span class="variable">address</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="number">7001</span>);</span><br><span class="line">        <span class="type">ServerSocketChannel</span> <span class="variable">serverSocketChannel</span> <span class="operator">=</span> ServerSocketChannel.open();</span><br><span class="line">        <span class="type">ServerSocket</span> <span class="variable">serverSocket</span> <span class="operator">=</span> serverSocketChannel.socket();</span><br><span class="line">        serverSocket.bind(address);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//创建buffer</span></span><br><span class="line">        <span class="type">ByteBuffer</span> <span class="variable">byteBuffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">4096</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">            <span class="type">SocketChannel</span> <span class="variable">socketChannel</span> <span class="operator">=</span> serverSocketChannel.accept();</span><br><span class="line">            <span class="type">int</span> <span class="variable">readcount</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">            <span class="keyword">while</span> (-<span class="number">1</span> != readcount) &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    readcount = socketChannel.read(byteBuffer);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (Exception ex) &#123;</span><br><span class="line">                    <span class="comment">// ex.printStackTrace();</span></span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">//</span></span><br><span class="line">                byteBuffer.rewind(); <span class="comment">//倒带 position = 0 mark 作废</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">NewIOClient.java</span><br><span class="line"></span><br><span class="line"><span class="keyword">package</span> com.atguigu.nio.zerocopy;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.FileInputStream;</span><br><span class="line"><span class="keyword">import</span> java.net.InetSocketAddress;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.FileChannel;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.SocketChannel;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NewIOClient</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">SocketChannel</span> <span class="variable">socketChannel</span> <span class="operator">=</span> SocketChannel.open();</span><br><span class="line">        socketChannel.connect(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="string">&quot;localhost&quot;</span>, <span class="number">7001</span>));</span><br><span class="line">        <span class="type">String</span> <span class="variable">filename</span> <span class="operator">=</span> <span class="string">&quot;protoc-3.6.1-win32.zip&quot;</span>;</span><br><span class="line">        <span class="comment">//得到一个文件channel</span></span><br><span class="line">        <span class="type">FileChannel</span> <span class="variable">fileChannel</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileInputStream</span>(filename).getChannel();</span><br><span class="line">        <span class="comment">//准备发送</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">startTime</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        <span class="comment">//在 linux 下一个 transferTo 方法就可以完成传输</span></span><br><span class="line">        <span class="comment">//在 windows 下一次调用 transferTo 只能发送 8m, 就需要分段传输文件,而且要主要</span></span><br><span class="line">        <span class="comment">//传输时的位置=》课后思考...</span></span><br><span class="line">        <span class="comment">//transferTo 底层使用到零拷贝</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">transferCount</span> <span class="operator">=</span> fileChannel.transferTo(<span class="number">0</span>, fileChannel.size(), socketChannel);</span><br><span class="line">        System.out.println(<span class="string">&quot;发送的总的字节数 = &quot;</span> + transferCount + <span class="string">&quot; 耗时: &quot;</span> + (System.currentTimeMillis() - startTime));</span><br><span class="line"></span><br><span class="line">        <span class="comment">//关闭</span></span><br><span class="line">        fileChannel.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="Java-AIO-基本介绍"><a href="#Java-AIO-基本介绍" class="headerlink" title="Java AIO 基本介绍"></a>Java AIO 基本介绍</h2><ol>
<li><code>JDK7</code> 引入了 <code>AsynchronousI/O</code>，即 <code>AIO</code>。在进行 <code>I/O</code> 编程中，常用到两种模式：<code>Reactor</code> 和 <code>Proactor</code>。<code>Java</code> 的 <code>NIO</code> 就是 <code>Reactor</code>，当有事件触发时，服务器端得到通知，进行相应的处理</li>
<li><code>AIO</code> 即 <code>NIO2.0</code>，叫做异步不阻塞的 <code>IO</code>。<code>AIO</code> 引入异步通道的概念，采用了 <code>Proactor</code> 模式，简化了程序编写，有效的请求才启动线程，它的特点是先由操作系统完成后才通知服务端程序启动线程去处理，一般适用于连接数较多且连接时间较长的应用</li>
<li>目前 <code>AIO</code> 还没有广泛应用，<code>Netty</code> 也是基于 <code>NIO</code>，而不是 <code>AIO</code>，因此我们就不详解 <code>AIO</code> 了，有兴趣的同学可以参考<a target="_blank" rel="noopener" href="http://www.52im.net/thread-306-1-1.html">《Java新一代网络编程模型AIO原理及Linux系统AIO介绍》</a></li>
</ol>
<h2 id="BIO、NIO、AIO-对比表"><a href="#BIO、NIO、AIO-对比表" class="headerlink" title="BIO、NIO、AIO 对比表"></a>BIO、NIO、AIO 对比表</h2><table>
<thead>
<tr>
<th align="center"></th>
<th align="center">BIO</th>
<th align="center">NIO</th>
<th align="center">AIO</th>
</tr>
</thead>
<tbody><tr>
<td align="center">IO模型</td>
<td align="center">同步阻塞</td>
<td align="center">同步非阻塞（多路复用）</td>
<td align="center">异步非阻塞</td>
</tr>
<tr>
<td align="center">编程难度</td>
<td align="center">简单</td>
<td align="center">复杂</td>
<td align="center">复杂</td>
</tr>
<tr>
<td align="center">可靠性</td>
<td align="center">差</td>
<td align="center">好</td>
<td align="center">好</td>
</tr>
<tr>
<td align="center">吞吐量</td>
<td align="center">低</td>
<td align="center">高</td>
<td align="center">高</td>
</tr>
</tbody></table>
<p><strong>举例说明</strong> </p>
<ol>
<li>同步阻塞：到理发店理发，就一直等理发师，直到轮到自己理发。</li>
<li>同步非阻塞：到理发店理发，发现前面有其它人理发，给理发师说下，先干其他事情，一会过来看是否轮到自己.</li>
<li>异步非阻塞：给理发师打电话，让理发师上门服务，自己干其它事情，理发师自己来家给你理发</li>
</ol>
</article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href="http://example.com">Alix</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="http://example.com/2022/04/08/netty/Netty%E5%85%A5%E9%97%A8-%E7%AC%AC%E4%B8%80%E8%AF%9D/">http://example.com/2022/04/08/netty/Netty%E5%85%A5%E9%97%A8-%E7%AC%AC%E4%B8%80%E8%AF%9D/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="http://example.com" target="_blank">石银的小酒屋</a>！</span></div></div><div class="tag_share"><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/Netty/">Netty</a></div><div class="post_share"><div class="social-share" data-image="https://npm.elemecdn.com/lql_static@latest/logo/netty_logo.jpg" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/css/share.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/js/social-share.min.js" defer></script></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/2022/04/15/netty/Netty%E5%85%A5%E9%97%A8-%E7%AC%AC%E4%BA%8C%E8%AF%9D/"><img class="prev-cover" src="https://npm.elemecdn.com/lql_static@latest/logo/netty_logo.jpg" onerror="onerror=null;src='/img/404.jpg'" alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">Netty入门-第二话</div></div></a></div><div class="next-post pull-right"><a href="/2021/11/19/JVM/JVM%E7%B3%BB%E5%88%97-%E7%AC%AC12%E7%AB%A0-%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E5%99%A8/"><img class="next-cover" src="https://npm.elemecdn.com/lql_static@latest/logo/jvm.png" onerror="onerror=null;src='/img/404.jpg'" alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">JVM系列-第12章-垃圾回收器</div></div></a></div></nav><div class="relatedPosts"><div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span>相关推荐</span></div><div class="relatedPosts-list"><div><a href="/2022/04/21/netty/Netty%E5%85%A5%E9%97%A8-%E7%AC%AC%E4%B8%89%E8%AF%9D/" title="Netty入门-第三话"><img class="cover" src="https://npm.elemecdn.com/lql_static@latest/logo/netty_logo.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-04-21</div><div class="title">Netty入门-第三话</div></div></a></div><div><a href="/2022/04/15/netty/Netty%E5%85%A5%E9%97%A8-%E7%AC%AC%E4%BA%8C%E8%AF%9D/" title="Netty入门-第二话"><img class="cover" src="https://npm.elemecdn.com/lql_static@latest/logo/netty_logo.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-04-15</div><div class="title">Netty入门-第二话</div></div></a></div></div></div><hr/><div id="post-comment"><div class="comment-head"><div class="comment-headline"><i class="fas fa-comments fa-fw"></i><span> 评论</span></div><div id="comment-switch"><span class="first-comment">Valine</span><span class="switch-btn"></span><span class="second-comment">Disqus</span></div></div><div class="comment-wrap"><div><div class="vcomment" id="vcomment"></div></div><div><div id="disqus_thread"></div></div></div></div></div><div class="aside-content" id="aside-content"><div class="card-widget card-info"><div class="is-center"><div class="avatar-img"><img src="/assetse/%E5%A4%B4%E5%83%8F.jpg" onerror="this.onerror=null;this.src='/img/friend_404.gif'" alt="avatar"/></div><div class="author-info__name">Alix</div><div class="author-info__description">归途也还可爱</div></div><div class="card-info-data site-data is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">23</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">9</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">7</div></a></div><a id="card-info-btn" target="_blank" rel="noopener" href="https://github.com/923584731"><i class="fab fa-github"></i><span>Follow Me</span></a><div class="card-info-social-icons is-center"><a class="social-icon" href="https://github.com/923584731" target="_blank" title="Github"><i class="fab fa-github"></i></a><a class="social-icon" href="mailto:923584731@qq.com" target="_blank" title="Email"><i class="fas fa-envelope"></i></a><a class="social-icon" href="/atom.xml" target="_blank" title="Rss"><i class="fas fa-rss"></i></a></div></div><div class="card-widget card-announcement"><div class="item-headline"><i class="fas fa-bullhorn fa-shake"></i><span>公告</span></div><div class="announcement_content">少年郎的博客网站</div></div><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span><span class="toc-percentage"></span></div><div class="toc-content"><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#Netty%E7%AE%80%E4%BB%8B"><span class="toc-number">1.</span> <span class="toc-text">Netty简介</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#Netty-%E7%9A%84%E4%BB%8B%E7%BB%8D"><span class="toc-number">1.1.</span> <span class="toc-text">Netty 的介绍</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Netty-%E7%9A%84%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF"><span class="toc-number">1.2.</span> <span class="toc-text">Netty 的应用场景</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BA%92%E8%81%94%E7%BD%91%E8%A1%8C%E4%B8%9A"><span class="toc-number">1.2.1.</span> <span class="toc-text">互联网行业</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%B8%B8%E6%88%8F%E8%A1%8C%E4%B8%9A"><span class="toc-number">1.2.2.</span> <span class="toc-text">游戏行业</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%A4%A7%E6%95%B0%E6%8D%AE%E9%A2%86%E5%9F%9F"><span class="toc-number">1.2.3.</span> <span class="toc-text">大数据领域</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%85%B6%E5%AE%83%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%BD%BF%E7%94%A8%E5%88%B0-Netty"><span class="toc-number">1.2.4.</span> <span class="toc-text">其它开源项目使用到 Netty</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Netty-%E7%9A%84%E5%AD%A6%E4%B9%A0%E8%B5%84%E6%96%99%E5%8F%82%E8%80%83"><span class="toc-number">1.3.</span> <span class="toc-text">Netty 的学习资料参考</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#Java-BIO%E7%BC%96%E7%A8%8B"><span class="toc-number">2.</span> <span class="toc-text">Java BIO编程</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#I-x2F-O-%E6%A8%A1%E5%9E%8B"><span class="toc-number">2.1.</span> <span class="toc-text">I&#x2F;O 模型</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#BIO%E3%80%81NIO%E3%80%81AIO-%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF%E5%88%86%E6%9E%90"><span class="toc-number">2.2.</span> <span class="toc-text">BIO、NIO、AIO 使用场景分析</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Java-BIO-%E5%9F%BA%E6%9C%AC%E4%BB%8B%E7%BB%8D"><span class="toc-number">2.3.</span> <span class="toc-text">Java BIO 基本介绍</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Java-BIO-%E5%B7%A5%E4%BD%9C%E6%9C%BA%E5%88%B6"><span class="toc-number">2.4.</span> <span class="toc-text">Java BIO 工作机制</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Java-BIO-%E5%BA%94%E7%94%A8%E5%AE%9E%E4%BE%8B"><span class="toc-number">2.5.</span> <span class="toc-text">Java BIO 应用实例</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90"><span class="toc-number">2.6.</span> <span class="toc-text">问题分析</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#Java-NIO%E7%BC%96%E7%A8%8B"><span class="toc-number">3.</span> <span class="toc-text">Java NIO编程</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#Java-NIO-%E5%9F%BA%E6%9C%AC%E4%BB%8B%E7%BB%8D"><span class="toc-number">3.1.</span> <span class="toc-text">Java NIO 基本介绍</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#NIO-%E5%92%8C-BIO-%E7%9A%84%E6%AF%94%E8%BE%83"><span class="toc-number">3.2.</span> <span class="toc-text">NIO 和 BIO 的比较</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#NIO-%E4%B8%89%E5%A4%A7%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E7%A4%BA%E6%84%8F%E5%9B%BE"><span class="toc-number">3.3.</span> <span class="toc-text">NIO 三大核心原理示意图</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Selector%E3%80%81Channel-%E5%92%8C-Buffer-%E5%85%B3%E7%B3%BB%E5%9B%BE%EF%BC%88%E7%AE%80%E5%8D%95%E7%89%88%EF%BC%89"><span class="toc-number">3.3.1.</span> <span class="toc-text">Selector、Channel 和 Buffer 关系图（简单版）</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%BC%93%E5%86%B2%E5%8C%BA%EF%BC%88Buffer%EF%BC%89"><span class="toc-number">3.4.</span> <span class="toc-text">缓冲区（Buffer）</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%9F%BA%E6%9C%AC%E4%BB%8B%E7%BB%8D"><span class="toc-number">3.4.1.</span> <span class="toc-text">基本介绍</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Buffer-%E7%B1%BB%E5%8F%8A%E5%85%B6%E5%AD%90%E7%B1%BB"><span class="toc-number">3.4.2.</span> <span class="toc-text">Buffer 类及其子类</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#ByteBuffer"><span class="toc-number">3.4.3.</span> <span class="toc-text">ByteBuffer</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%80%9A%E9%81%93%EF%BC%88Channel%EF%BC%89"><span class="toc-number">3.5.</span> <span class="toc-text">通道（Channel）</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%9F%BA%E6%9C%AC%E4%BB%8B%E7%BB%8D-1"><span class="toc-number">3.5.1.</span> <span class="toc-text">基本介绍</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#FileChannel-%E7%B1%BB"><span class="toc-number">3.5.2.</span> <span class="toc-text">FileChannel 类</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%BA%94%E7%94%A8%E5%AE%9E%E4%BE%8B1-%E6%9C%AC%E5%9C%B0%E6%96%87%E4%BB%B6%E5%86%99%E6%95%B0%E6%8D%AE"><span class="toc-number">3.5.3.</span> <span class="toc-text">应用实例1 - 本地文件写数据</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%BA%94%E7%94%A8%E5%AE%9E%E4%BE%8B2-%E6%9C%AC%E5%9C%B0%E6%96%87%E4%BB%B6%E8%AF%BB%E6%95%B0%E6%8D%AE"><span class="toc-number">3.5.4.</span> <span class="toc-text">应用实例2 - 本地文件读数据</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%BA%94%E7%94%A8%E5%AE%9E%E4%BE%8B3-%E4%BD%BF%E7%94%A8%E4%B8%80%E4%B8%AA-Buffer-%E5%AE%8C%E6%88%90%E6%96%87%E4%BB%B6%E8%AF%BB%E5%8F%96%E3%80%81%E5%86%99%E5%85%A5"><span class="toc-number">3.5.5.</span> <span class="toc-text">应用实例3 - 使用一个 Buffer 完成文件读取、写入</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%BA%94%E7%94%A8%E5%AE%9E%E4%BE%8B4-%E6%8B%B7%E8%B4%9D%E6%96%87%E4%BB%B6-transferFrom-%E6%96%B9%E6%B3%95"><span class="toc-number">3.5.6.</span> <span class="toc-text">应用实例4 - 拷贝文件 transferFrom 方法</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%85%B3%E4%BA%8E-Buffer-%E5%92%8C-Channel-%E7%9A%84%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9%E5%92%8C%E7%BB%86%E8%8A%82"><span class="toc-number">3.5.7.</span> <span class="toc-text">关于 Buffer 和 Channel 的注意事项和细节</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Selector%EF%BC%88%E9%80%89%E6%8B%A9%E5%99%A8%EF%BC%89"><span class="toc-number">3.6.</span> <span class="toc-text">Selector（选择器）</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%9F%BA%E6%9C%AC%E4%BB%8B%E7%BB%8D-2"><span class="toc-number">3.6.1.</span> <span class="toc-text">基本介绍</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Selector-%E7%A4%BA%E6%84%8F%E5%9B%BE%E5%92%8C%E7%89%B9%E7%82%B9%E8%AF%B4%E6%98%8E"><span class="toc-number">3.6.2.</span> <span class="toc-text">Selector 示意图和特点说明</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Selector-%E7%B1%BB%E7%9B%B8%E5%85%B3%E6%96%B9%E6%B3%95"><span class="toc-number">3.6.3.</span> <span class="toc-text">Selector 类相关方法</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9"><span class="toc-number">3.6.4.</span> <span class="toc-text">注意事项</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#NIO-%E9%9D%9E%E9%98%BB%E5%A1%9E%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90%E5%9B%BE"><span class="toc-number">3.7.</span> <span class="toc-text">NIO 非阻塞网络编程原理分析图</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#NIO-%E9%9D%9E%E9%98%BB%E5%A1%9E%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8"><span class="toc-number">3.8.</span> <span class="toc-text">NIO 非阻塞网络编程快速入门</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#SelectionKey"><span class="toc-number">3.8.1.</span> <span class="toc-text">SelectionKey</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#ServerSocketChannel"><span class="toc-number">3.8.2.</span> <span class="toc-text">ServerSocketChannel</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#SocketChannel"><span class="toc-number">3.8.3.</span> <span class="toc-text">SocketChannel</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#NIO%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B%E5%BA%94%E7%94%A8%E5%AE%9E%E4%BE%8B-%E7%BE%A4%E8%81%8A%E7%B3%BB%E7%BB%9F"><span class="toc-number">3.9.</span> <span class="toc-text">NIO网络编程应用实例 - 群聊系统</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#NIO%E4%B8%8E%E9%9B%B6%E6%8B%B7%E8%B4%9D"><span class="toc-number">3.10.</span> <span class="toc-text">NIO与零拷贝</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E9%9B%B6%E6%8B%B7%E8%B4%9D%E5%9F%BA%E6%9C%AC%E4%BB%8B%E7%BB%8D"><span class="toc-number">3.10.1.</span> <span class="toc-text">零拷贝基本介绍</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BC%A0%E7%BB%9F-IO-%E6%95%B0%E6%8D%AE%E8%AF%BB%E5%86%99"><span class="toc-number">3.10.2.</span> <span class="toc-text">传统 IO 数据读写</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BC%A0%E7%BB%9F-IO-%E6%A8%A1%E5%9E%8B"><span class="toc-number">3.10.3.</span> <span class="toc-text">传统 IO 模型</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#mmap-%E4%BC%98%E5%8C%96"><span class="toc-number">3.10.4.</span> <span class="toc-text">mmap 优化</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#sendFile-%E4%BC%98%E5%8C%96"><span class="toc-number">3.10.5.</span> <span class="toc-text">sendFile 优化</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E9%9B%B6%E6%8B%B7%E8%B4%9D%E7%9A%84%E5%86%8D%E6%AC%A1%E7%90%86%E8%A7%A3"><span class="toc-number">3.10.6.</span> <span class="toc-text">零拷贝的再次理解</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#mmap-%E5%92%8C-sendFile-%E7%9A%84%E5%8C%BA%E5%88%AB"><span class="toc-number">3.10.7.</span> <span class="toc-text">mmap 和 sendFile 的区别</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#NIO-%E9%9B%B6%E6%8B%B7%E8%B4%9D%E6%A1%88%E4%BE%8B"><span class="toc-number">3.10.8.</span> <span class="toc-text">NIO 零拷贝案例</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Java-AIO-%E5%9F%BA%E6%9C%AC%E4%BB%8B%E7%BB%8D"><span class="toc-number">3.11.</span> <span class="toc-text">Java AIO 基本介绍</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#BIO%E3%80%81NIO%E3%80%81AIO-%E5%AF%B9%E6%AF%94%E8%A1%A8"><span class="toc-number">3.12.</span> <span class="toc-text">BIO、NIO、AIO 对比表</span></a></li></ol></li></ol></div></div><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>最新文章</span></div><div class="aside-list"><div class="aside-list-item"><a class="thumbnail" href="/2023/04/20/%E9%94%81%E6%9C%BA%E5%88%B6/" title="数据库并发锁机制"><img src="/assetse/y13.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="数据库并发锁机制"/></a><div class="content"><a class="title" href="/2023/04/20/%E9%94%81%E6%9C%BA%E5%88%B6/" title="数据库并发锁机制">数据库并发锁机制</a><time datetime="2023-04-20T06:55:04.000Z" title="发表于 2023-04-20 14:55:04">2023-04-20</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/2022/04/21/netty/Netty%E5%85%A5%E9%97%A8-%E7%AC%AC%E4%B8%89%E8%AF%9D/" title="Netty入门-第三话"><img src="https://npm.elemecdn.com/lql_static@latest/logo/netty_logo.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="Netty入门-第三话"/></a><div class="content"><a class="title" href="/2022/04/21/netty/Netty%E5%85%A5%E9%97%A8-%E7%AC%AC%E4%B8%89%E8%AF%9D/" title="Netty入门-第三话">Netty入门-第三话</a><time datetime="2022-04-21T09:38:58.000Z" title="发表于 2022-04-21 17:38:58">2022-04-21</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/2022/04/15/netty/Netty%E5%85%A5%E9%97%A8-%E7%AC%AC%E4%BA%8C%E8%AF%9D/" title="Netty入门-第二话"><img src="https://npm.elemecdn.com/lql_static@latest/logo/netty_logo.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="Netty入门-第二话"/></a><div class="content"><a class="title" href="/2022/04/15/netty/Netty%E5%85%A5%E9%97%A8-%E7%AC%AC%E4%BA%8C%E8%AF%9D/" title="Netty入门-第二话">Netty入门-第二话</a><time datetime="2022-04-15T06:21:58.000Z" title="发表于 2022-04-15 14:21:58">2022-04-15</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/2022/04/08/netty/Netty%E5%85%A5%E9%97%A8-%E7%AC%AC%E4%B8%80%E8%AF%9D/" title="Netty入门-第一话"><img src="https://npm.elemecdn.com/lql_static@latest/logo/netty_logo.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="Netty入门-第一话"/></a><div class="content"><a class="title" href="/2022/04/08/netty/Netty%E5%85%A5%E9%97%A8-%E7%AC%AC%E4%B8%80%E8%AF%9D/" title="Netty入门-第一话">Netty入门-第一话</a><time datetime="2022-04-08T06:21:58.000Z" title="发表于 2022-04-08 14:21:58">2022-04-08</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/2021/11/19/JVM/JVM%E7%B3%BB%E5%88%97-%E7%AC%AC12%E7%AB%A0-%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E5%99%A8/" title="JVM系列-第12章-垃圾回收器"><img src="https://npm.elemecdn.com/lql_static@latest/logo/jvm.png" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="JVM系列-第12章-垃圾回收器"/></a><div class="content"><a class="title" href="/2021/11/19/JVM/JVM%E7%B3%BB%E5%88%97-%E7%AC%AC12%E7%AB%A0-%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E5%99%A8/" title="JVM系列-第12章-垃圾回收器">JVM系列-第12章-垃圾回收器</a><time datetime="2021-11-19T10:33:24.000Z" title="发表于 2021-11-19 18:33:24">2021-11-19</time></div></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">&copy;2020 - 2023 By Alix</div><div class="framework-info"><span>框架 </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><a id="to_comment" href="#post-comment" title="直达评论"><i class="fas fa-comments"></i></a><button id="go-up" type="button" title="回到顶部"><i class="fas fa-arrow-up"></i></button></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.umd.min.js"></script><script>var preloader = {
  endLoading: () => {
    document.body.style.overflow = 'auto';
    document.getElementById('loading-box').classList.add("loaded")
  },
  initLoading: () => {
    document.body.style.overflow = '';
    document.getElementById('loading-box').classList.remove("loaded")

  }
}
window.addEventListener('load',preloader.endLoading())</script><div class="js-pjax"><script>function loadValine () {
  function initValine () {
    const valine = new Valine(Object.assign({
      el: '#vcomment',
      appId: '',
      appKey: '',
      avatar: 'monsterid',
      serverURLs: '',
      emojiMaps: "",
      path: window.location.pathname,
      visitor: false
    }, null))
  }

  if (typeof Valine === 'function') initValine() 
  else getScript('https://cdn.jsdelivr.net/npm/valine/dist/Valine.min.js').then(initValine)
}

if ('Valine' === 'Valine' || !true) {
  if (true) btf.loadComment(document.getElementById('vcomment'),loadValine)
  else setTimeout(loadValine, 0)
} else {
  function loadOtherComment () {
    loadValine()
  }
}</script><script>function loadDisqus () {
  var disqus_config = function () {
    this.page.url = 'http://example.com/2022/04/08/netty/Netty%E5%85%A5%E9%97%A8-%E7%AC%AC%E4%B8%80%E8%AF%9D/'
    this.page.identifier = '/2022/04/08/netty/Netty%E5%85%A5%E9%97%A8-%E7%AC%AC%E4%B8%80%E8%AF%9D/'
    this.page.title = 'Netty入门-第一话'
  };

  window.disqusReset = () => {
    DISQUS.reset({
      reload: true,
      config: disqus_config
    })
  }

  if (window.DISQUS) disqusReset()
  else {
    (function() { 
      var d = document, s = d.createElement('script');
      s.src = 'https://.disqus.com/embed.js';
      s.setAttribute('data-timestamp', +new Date());
      (d.head || d.body).appendChild(s);
    })();
  }

  document.getElementById('darkmode').addEventListener('click', () => {
    setTimeout(() => window.disqusReset(), 200)
  })
}

if ('Valine' === 'Disqus' || !true) {
  if (true) btf.loadComment(document.getElementById('disqus_thread'), loadDisqus)
  else loadDisqus()
} else {
  function loadOtherComment () {
    loadDisqus()
  }
}
</script></div><script async src="https://npm.elemecdn.com/tzy-blog/lib/js/other/sakura.js"></script><script defer="defer" id="ribbon" src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/dist/canvas-ribbon.min.js" size="150" alpha="0.6" zIndex="-1" mobile="false" data-click="false"></script><script defer="defer" id="fluttering_ribbon" mobile="false" src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/dist/canvas-fluttering-ribbon.min.js"></script><script id="click-show-text" src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/dist/click-show-text.min.js" data-mobile="false" data-text="你好,大帅哥,大美女" data-fontsize="15px" data-random="true" async="async"></script><script src="https://cdn.jsdelivr.net/npm/pjax/pjax.min.js"></script><script>let pjaxSelectors = ["head > title","#config-diff","#body-wrap","#rightside-config-hide","#rightside-config-show",".js-pjax"]

var pjax = new Pjax({
  elements: 'a:not([target="_blank"])',
  selectors: pjaxSelectors,
  cacheBust: false,
  analytics: false,
  scrollRestoration: false
})

document.addEventListener('pjax:send', function () {

  // removeEventListener scroll 
  window.tocScrollFn && window.removeEventListener('scroll', window.tocScrollFn)
  window.scrollCollect && window.removeEventListener('scroll', scrollCollect)

  typeof preloader === 'object' && preloader.initLoading()
  document.getElementById('rightside').style.cssText = "opacity: ''; transform: ''"
  
  if (window.aplayers) {
    for (let i = 0; i < window.aplayers.length; i++) {
      if (!window.aplayers[i].options.fixed) {
        window.aplayers[i].destroy()
      }
    }
  }

  typeof typed === 'object' && typed.destroy()

  //reset readmode
  const $bodyClassList = document.body.classList
  $bodyClassList.contains('read-mode') && $bodyClassList.remove('read-mode')

  typeof disqusjs === 'object' && disqusjs.destroy()
})

document.addEventListener('pjax:complete', function () {
  window.refreshFn()

  document.querySelectorAll('script[data-pjax]').forEach(item => {
    const newScript = document.createElement('script')
    const content = item.text || item.textContent || item.innerHTML || ""
    Array.from(item.attributes).forEach(attr => newScript.setAttribute(attr.name, attr.value))
    newScript.appendChild(document.createTextNode(content))
    item.parentNode.replaceChild(newScript, item)
  })

  GLOBAL_CONFIG.islazyload && window.lazyLoadInstance.update()

  typeof chatBtnFn === 'function' && chatBtnFn()
  typeof panguInit === 'function' && panguInit()

  // google analytics
  typeof gtag === 'function' && gtag('config', '', {'page_path': window.location.pathname});

  // baidu analytics
  typeof _hmt === 'object' && _hmt.push(['_trackPageview',window.location.pathname]);

  typeof loadMeting === 'function' && document.getElementsByClassName('aplayer').length && loadMeting()

  // prismjs
  typeof Prism === 'object' && Prism.highlightAll()

  typeof preloader === 'object' && preloader.endLoading()
})

document.addEventListener('pjax:error', (e) => {
  if (e.request.status === 404) {
    pjax.loadUrl('/404.html')
  }
})</script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div><script src="/live2dw/lib/L2Dwidget.min.js?094cbace49a39548bed64abff5988b05"></script><script>L2Dwidget.init({"tagMode":false,"debug":false,"model":{"jsonPath":"/live2dw/assets/shizuku.model.json"},"display":{"position":"right","width":150,"height":300,"hOffset":20,"vOffset":-20},"mobile":{"show":false},"log":false,"pluginJsPath":"lib/","pluginModelPath":"assets/","pluginRootPath":"live2dw/"});</script></body></html>